← luna

Commit 0dd2107

6
files
+395
added
-5
deleted
@@ -28,6 +28,8 @@ pub struct SurfaceData {
28 28 pub slot: Mutex<Option<usize>>,
29 29 /// Powiązana powierzchnia warstwowa (`wlr-layer-shell`), jeśli klient ją utworzył.
30 30 pub layer: Mutex<Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>>,
31 + /// Uchwyty `zwlr_foreign_toplevel` (gdy to okno najwyższego poziomu).
32 + pub toplevel: Mutex<Option<crate::foreign_toplevel::ToplevelInfo>>,
31 33 }
32 34
33 35 impl SurfaceData {
@@ -38,6 +40,7 @@ impl SurfaceData {
38 40 buffer: Mutex::new(None),
39 41 slot: Mutex::new(None),
40 42 layer: Mutex::new(None),
43 + toplevel: Mutex::new(None),
41 44 }
42 45 }
43 46 }
44 47 new file mode 100644
@@ -0,0 +1,342 @@
1 +//! `zwlr_foreign_toplevel_management` (unstable v1) — lista okien dla powłoki.
2 +//!
3 +//! Kompozytor wystawia globalny menedżer; każdy klient (np. pasek `luna-shell`) dostaje
4 +//! **uchwyty** do wszystkich okien najwyższego poziomu i powiadomienia o zmianach
5 +//! (tytuł, `app_id`, stan: aktywny/zminimalizowany/zmaksymalizowany/fullscreen, zamknięcie).
6 +//! Klient może poprosić o aktywację albo zamknięcie okna — to czyni pasek użytecznym.
7 +
8 +use wayland_protocols_wlr::foreign_toplevel::v1::server::{
9 + zwlr_foreign_toplevel_handle_v1 as handle, zwlr_foreign_toplevel_manager_v1 as manager,
10 +};
11 +use wayland_server::{
12 + protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
13 + New, Resource,
14 +};
15 +
16 +use crate::compositor::SurfaceData;
17 +use crate::seat;
18 +use crate::state::State;
19 +use crate::sync::LockExt;
20 +
21 +/// Stany okna (`zwlr_foreign_toplevel_handle_v1.state`).
22 +pub const STATE_MAXIMIZED: u32 = 0;
23 +pub const STATE_MINIMIZED: u32 = 1;
24 +pub const STATE_ACTIVATED: u32 = 2;
25 +pub const STATE_FULLSCREEN: u32 = 3;
26 +
27 +/// Uchwyt okna po stronie klienta (`zwlr_foreign_toplevel_handle_v1`).
28 +pub struct ToplevelHandleData {
29 + /// Powierzchnia, którą ten uchwyt reprezentuje.
30 + pub surface: WlSurface,
31 +}
32 +
33 +/// Informacje o toplevelu trzymane przy powierzchni (do rozsyłania do menedżerów).
34 +#[derive(Default)]
35 +pub struct ToplevelInfo {
36 + pub title: String,
37 + pub app_id: String,
38 + /// Uchwyty utworzone dla podłączonych menedżerów (po jednym na klienta).
39 + pub handles: Vec<handle::ZwlrForeignToplevelHandleV1>,
40 +}
41 +
42 +impl GlobalDispatch<manager::ZwlrForeignToplevelManagerV1, ()> for State {
43 + fn bind(
44 + state: &mut Self,
45 + handle: &DisplayHandle,
46 + _client: &Client,
47 + resource: New<manager::ZwlrForeignToplevelManagerV1>,
48 + _data: &(),
49 + data_init: &mut DataInit<'_, Self>,
50 + ) {
51 + let mgr = data_init.init(resource, ());
52 + // Nowo podłączony menedżer: zgłoś istniejące okna.
53 + for surface in state.slots.clone() {
54 + if is_toplevel(&surface) {
55 + announce(state, handle, &mgr, &surface);
56 + }
57 + }
58 + state.toplevel_managers.push(mgr);
59 + }
60 +}
61 +
62 +impl Dispatch<manager::ZwlrForeignToplevelManagerV1, ()> for State {
63 + fn request(
64 + state: &mut Self,
65 + _client: &Client,
66 + resource: &manager::ZwlrForeignToplevelManagerV1,
67 + request: manager::Request,
68 + _data: &(),
69 + _handle: &DisplayHandle,
70 + _data_init: &mut DataInit<'_, Self>,
71 + ) {
72 + if let manager::Request::Stop = request {
73 + // Klient kończy: zdejmij menedżera i wszystkie jego uchwyty.
74 + let mgr_id = resource.id();
75 + let client_id = resource.client().map(|c| c.id());
76 + state.toplevel_managers.retain(|m| m.id() != mgr_id);
77 + if let Some(client_id) = client_id {
78 + for surface in state.slots.clone() {
79 + if let Some(sd) = surface.data::<SurfaceData>() {
80 + if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
81 + info.handles.retain(|h| {
82 + h.client().map(|c| c.id() != client_id).unwrap_or(false)
83 + });
84 + }
85 + }
86 + }
87 + }
88 + }
89 + }
90 +}
91 +
92 +impl Dispatch<handle::ZwlrForeignToplevelHandleV1, ToplevelHandleData> for State {
93 + fn request(
94 + state: &mut Self,
95 + _client: &Client,
96 + resource: &handle::ZwlrForeignToplevelHandleV1,
97 + request: handle::Request,
98 + data: &ToplevelHandleData,
99 + _handle: &DisplayHandle,
100 + _data_init: &mut DataInit<'_, Self>,
101 + ) {
102 + match request {
103 + handle::Request::Activate { .. } => {
104 + if let Some(slot) = slot_of(&data.surface) {
105 + seat::activate_slot(state, slot);
106 + broadcast_states(state);
107 + }
108 + }
109 + handle::Request::Close => {
110 + seat::close_surface(state, &data.surface);
111 + }
112 + handle::Request::Destroy => {
113 + forget_handle(&data.surface, resource.id());
114 + }
115 + // Maksymalizacja/minimalizacja/fullscreen/rect: poza zakresem bieżącego kroku.
116 + _ => {}
117 + }
118 + }
119 +}
120 +
121 +/// Czy powierzchnia jest oknem najwyższego poziomu (`xdg_toplevel`, nie popup/layer)?
122 +fn is_toplevel(surface: &WlSurface) -> bool {
123 + surface
124 + .data::<SurfaceData>()
125 + .map(|sd| sd.toplevel.lock_or_recover().is_some())
126 + .unwrap_or(false)
127 +}
128 +
129 +/// Slot okna w modelu dla powierzchni.
130 +fn slot_of(surface: &WlSurface) -> Option<usize> {
131 + surface
132 + .data::<SurfaceData>()
133 + .and_then(|sd| *sd.slot.lock_or_recover())
134 +}
135 +
136 +/// Rejestruje okno jako toplevel (wołane przy `get_toplevel`).
137 +pub fn register_toplevel(surface: &WlSurface) {
138 + if let Some(sd) = surface.data::<SurfaceData>() {
139 + let mut t = sd.toplevel.lock_or_recover();
140 + if t.is_none() {
141 + *t = Some(ToplevelInfo::default());
142 + }
143 + }
144 +}
145 +
146 +/// Zgłasza nowo podłączonemu menedżerowi istniejące okno (uchwyt + pierwszy stan).
147 +pub fn announce(
148 + state: &State,
149 + display: &DisplayHandle,
150 + mgr: &manager::ZwlrForeignToplevelManagerV1,
151 + surface: &WlSurface,
152 +) {
153 + let Some(client) = mgr.client() else {
154 + return;
155 + };
156 + let Ok(handle) = client
157 + .create_resource::<handle::ZwlrForeignToplevelHandleV1, ToplevelHandleData, State>(
158 + display,
159 + 3,
160 + ToplevelHandleData {
161 + surface: surface.clone(),
162 + },
163 + )
164 + else {
165 + return;
166 + };
167 + let _ = mgr.send_event(manager::Event::Toplevel {
168 + toplevel: handle.clone(),
169 + });
170 + if let Some(sd) = surface.data::<SurfaceData>() {
171 + if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
172 + info.handles.push(handle.clone());
173 + }
174 + }
175 + send_full(state, surface, &handle);
176 +}
177 +
178 +/// Rozsyła aktualizację tytułu do wszystkich menedżerów.
179 +pub fn broadcast_title(state: &State, surface: &WlSurface, title: &str) {
180 + for_each_handle(surface, |h| {
181 + let _ = h.send_event(handle::Event::Title {
182 + title: title.to_owned(),
183 + });
184 + let _ = h.send_event(handle::Event::Done);
185 + });
186 + let _ = state;
187 +}
188 +
189 +/// Rozsyła aktualizację `app_id`.
190 +pub fn broadcast_app_id(state: &State, surface: &WlSurface, app_id: &str) {
191 + for_each_handle(surface, |h| {
192 + let _ = h.send_event(handle::Event::AppId {
193 + app_id: app_id.to_owned(),
194 + });
195 + let _ = h.send_event(handle::Event::Done);
196 + });
197 + let _ = state;
198 +}
199 +
200 +/// Rozsyła stan (aktywny/minimalizowany/…) wszystkich okien — po zmianie fokusu.
201 +pub fn broadcast_states(state: &State) {
202 + for surface in state.slots.clone() {
203 + if !is_toplevel(&surface) {
204 + continue;
205 + }
206 + let Some(slot) = slot_of(&surface) else {
207 + continue;
208 + };
209 + let bytes = state_bytes(state, slot);
210 + for_each_handle(&surface, |h| {
211 + let _ = h.send_event(handle::Event::State {
212 + state: bytes.clone(),
213 + });
214 + let _ = h.send_event(handle::Event::Done);
215 + });
216 + }
217 +}
218 +
219 +/// Powiadamia o zamknięciu okna (i zwalnia uchwyty).
220 +pub fn announce_closed(surface: &WlSurface) {
221 + if let Some(sd) = surface.data::<SurfaceData>() {
222 + if let Some(mut info) = sd.toplevel.lock_or_recover().take() {
223 + for h in info.handles.drain(..) {
224 + let _ = h.send_event(handle::Event::Closed);
225 + }
226 + }
227 + }
228 +}
229 +
230 +/// Pełny stan okna wysyłany nowemu uchwytowi (title + app_id + stan + `done`).
231 +fn send_full(state: &State, surface: &WlSurface, handle: &handle::ZwlrForeignToplevelHandleV1) {
232 + let (title, app_id) = toplevel_text(surface);
233 + let _ = handle.send_event(handle::Event::Title { title });
234 + let _ = handle.send_event(handle::Event::AppId { app_id });
235 + if let Some(slot) = slot_of(surface) {
236 + let _ = handle.send_event(handle::Event::State {
237 + state: state_bytes(state, slot),
238 + });
239 + }
240 + let _ = handle.send_event(handle::Event::Done);
241 +}
242 +
243 +/// `(title, app_id)` okna z jego danych.
244 +fn toplevel_text(surface: &WlSurface) -> (String, String) {
245 + surface
246 + .data::<SurfaceData>()
247 + .and_then(|sd| {
248 + sd.toplevel
249 + .lock_or_recover()
250 + .as_ref()
251 + .map(|t| (t.title.clone(), t.app_id.clone()))
252 + })
253 + .unwrap_or_default()
254 +}
255 +
256 +/// Bity stanu okna (`slot`) zakodowane jako tablica `u32` (native endian).
257 +fn state_bytes(state: &State, slot: usize) -> Vec<u8> {
258 + let focused = state.keyboard_focus.as_ref().map(|(s, _)| *s);
259 + let mut flags: Vec<u32> = Vec::new();
260 + {
261 + let windows = state.windows.lock_or_recover();
262 + if let Some(w) = windows.get(slot) {
263 + if w.maximized {
264 + flags.push(STATE_MAXIMIZED);
265 + }
266 + if w.minimized {
267 + flags.push(STATE_MINIMIZED);
268 + }
269 + if w.fullscreen {
270 + flags.push(STATE_FULLSCREEN);
271 + }
272 + }
273 + }
274 + if focused == Some(slot) {
275 + flags.push(STATE_ACTIVATED);
276 + }
277 + let mut out = Vec::with_capacity(flags.len() * 4);
278 + for f in flags {
279 + out.extend_from_slice(&f.to_ne_bytes());
280 + }
281 + out
282 +}
283 +
284 +fn for_each_handle(surface: &WlSurface, mut f: impl FnMut(&handle::ZwlrForeignToplevelHandleV1)) {
285 + let handles = surface
286 + .data::<SurfaceData>()
287 + .and_then(|sd| {
288 + sd.toplevel
289 + .lock_or_recover()
290 + .as_ref()
291 + .map(|t| t.handles.clone())
292 + })
293 + .unwrap_or_default();
294 + for h in &handles {
295 + f(h);
296 + }
297 +}
298 +
299 +/// Dezaktywuje pojedynczy uchwyt (po jego `destroy`).
300 +fn forget_handle(surface: &WlSurface, id: wayland_server::backend::ObjectId) {
301 + if let Some(sd) = surface.data::<SurfaceData>() {
302 + if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
303 + info.handles.retain(|h| h.id() != id);
304 + }
305 + }
306 +}
307 +
308 +#[cfg(test)]
309 +mod tests {
310 + use super::*;
311 +
312 + #[test]
313 + fn state_encoding_is_one_u32_per_flag() {
314 + let mut s = State::default();
315 + s.windows.lock_or_recover().push(crate::state::Window {
316 + x: 0.0,
317 + y: 0.0,
318 + width: 10,
319 + height: 10,
320 + z: 1,
321 + title: "t".into(),
322 + popup: false,
323 + layer: false,
324 + mapped: true,
325 + minimized: true,
326 + maximized: true,
327 + fullscreen: false,
328 + saved: None,
329 + data: std::sync::Arc::new(libluna_render::ImageData::new(1, 1, vec![0, 0, 0, 0])),
330 + });
331 + let bytes = state_bytes(&s, 0);
332 + assert_eq!(bytes.len(), 4 * 2); // maximized + minimized (brak fokusu)
333 + assert_eq!(
334 + u32::from_ne_bytes(bytes[0..4].try_into().unwrap()),
335 + STATE_MAXIMIZED
336 + );
337 + assert_eq!(
338 + u32::from_ne_bytes(bytes[4..8].try_into().unwrap()),
339 + STATE_MINIMIZED
340 + );
341 + }
342 +}
@@ -10,6 +10,7 @@ mod data;
10 10 mod decoration;
11 11 mod drm;
12 12 mod evdev;
13 +mod foreign_toplevel;
13 14 mod input;
14 15 mod layer;
15 16 mod monitors;
@@ -28,6 +29,7 @@ use std::sync::Arc;
28 29 use libluna_core::Event;
29 30 use wayland_protocols::xdg::decoration::zv1::server::zxdg_decoration_manager_v1;
30 31 use wayland_protocols::xdg::shell::server::xdg_wm_base;
32 +use wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
31 33 use wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_shell_v1;
32 34 use wayland_server::protocol::{wl_compositor, wl_data_device_manager, wl_output, wl_seat, wl_shm};
33 35 use wayland_server::{Display, ListeningSocket};
@@ -86,6 +88,7 @@ fn main() -> Result<(), Box<dyn Error>> {
86 88 handle.create_global::<State, zwlr_layer_shell_v1::ZwlrLayerShellV1, ()>(4, ());
87 89 handle.create_global::<State, wl_data_device_manager::WlDataDeviceManager, ()>(3, ());
88 90 handle.create_global::<State, wl_seat::WlSeat, ()>(1, ());
91 + handle.create_global::<State, ZwlrForeignToplevelManagerV1, ()>(3, ());
89 92
90 93 // Gniazdo nasłuchu: `wayland-0`, `wayland-1`, …
91 94 ensure_runtime_dir()?;
@@ -586,6 +586,7 @@ fn toggle_minimize(state: &mut State, slot: usize) {
586 586 if minimized {
587 587 clear_focus_if(state, slot);
588 588 }
589 + crate::foreign_toplevel::broadcast_states(state);
589 590 }
590 591
591 592 /// Przełącza maksymalizację (zapamiętuje/odtwarza geometrię).
@@ -629,6 +630,7 @@ fn toggle_maximize(state: &mut State, slot: usize) {
629 630 .map(|(_, _, w, h)| (w, h))
630 631 .unwrap_or((ow, oh));
631 632 send_configure(state, slot, nw, nh);
633 + crate::foreign_toplevel::broadcast_states(state);
632 634 }
633 635
634 636 /// Przełącza pełny ekran (ukrywa dekorację).
@@ -672,6 +674,7 @@ fn toggle_fullscreen(state: &mut State, slot: usize) {
672 674 .map(|(_, _, w, h)| (w, h))
673 675 .unwrap_or((ow, oh));
674 676 send_configure(state, slot, nw, nh);
677 + crate::foreign_toplevel::broadcast_states(state);
675 678 }
676 679
677 680 /// Zdejmuje fokus klawiatury/wskaźnika z okna (np. po minimalizacji).
@@ -687,6 +690,7 @@ fn clear_focus_if(state: &mut State, slot: usize) {
687 690 if state.pointer_focus.as_ref().map(|(s, _)| *s) == Some(slot) {
688 691 pointer_leave(state);
689 692 }
693 + crate::foreign_toplevel::broadcast_states(state);
690 694 }
691 695
692 696 /// Prosi klienta o zamknięcie okna (`xdg_toplevel.close`).
@@ -719,6 +723,21 @@ fn raise_and_focus(state: &mut State, slot: usize) {
719 723 }
720 724 }
721 725 set_keyboard_focus(state, slot);
726 + crate::foreign_toplevel::broadcast_states(state);
727 +}
728 +
729 +/// Aktywuje okno o podanym slocie (z `zwlr_foreign_toplevel_handle_v1.activate`).
730 +pub fn activate_slot(state: &mut State, slot: usize) {
731 + raise_and_focus(state, slot);
732 +}
733 +
734 +/// Zamyka okno reprezentowane przez `surface` (z `...handle_v1.close`).
735 +pub fn close_surface(state: &mut State, surface: &wayland_server::protocol::wl_surface::WlSurface) {
736 + if let Some(sd) = surface.data::<SurfaceData>() {
737 + if let Some(slot) = *sd.slot.lock_or_recover() {
738 + send_close(state, slot);
739 + }
740 + }
722 741 }
723 742
724 743 fn set_keyboard_focus(state: &mut State, slot: usize) {
@@ -128,18 +128,24 @@ impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
128 128
129 129 impl Dispatch<xdg_surface::XdgSurface, XdgSurfaceData> for State {
130 130 fn request(
131 - _state: &mut Self,
131 + state: &mut Self,
132 132 _client: &Client,
133 133 _resource: &xdg_surface::XdgSurface,
134 134 request: xdg_surface::Request,
135 135 data: &XdgSurfaceData,
136 - _handle: &DisplayHandle,
136 + handle: &DisplayHandle,
137 137 data_init: &mut DataInit<'_, Self>,
138 138 ) {
139 139 match request {
140 140 xdg_surface::Request::GetToplevel { id } => {
141 141 let toplevel = data_init.init(id, XdgToplevelData::new(data.wl_surface.clone()));
142 142 *data.toplevel.lock_or_recover() = Some(toplevel);
143 + // Zarejestruj okno dla paska (foreign-toplevel) i zgłoś menedżerom.
144 + crate::foreign_toplevel::register_toplevel(&data.wl_surface);
145 + let managers = state.toplevel_managers.clone();
146 + for mgr in &managers {
147 + crate::foreign_toplevel::announce(state, handle, mgr, &data.wl_surface);
148 + }
143 149 }
144 150 xdg_surface::Request::GetPopup {
145 151 id,
@@ -180,17 +186,30 @@ impl Dispatch<xdg_toplevel::XdgToplevel, XdgToplevelData> for State {
180 186 xdg_toplevel::Request::SetTitle { title } => {
181 187 *data.title.lock_or_recover() = title.clone();
182 188 state.toplevels += 1;
183 - // Odśwież tytuł w modelu okien (dekoracja rysuje go na pasku).
189 + // Odśwież tytuł w modelu okien i rozgłoś do pasków (foreign-toplevel).
184 190 if let Some(sd) = data.surface.data::<SurfaceData>() {
191 + if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
192 + info.title = title.clone();
193 + }
185 194 if let Some(slot) = *sd.slot.lock_or_recover() {
186 195 if let Some(window) = state.windows.lock_or_recover().get_mut(slot) {
187 - window.title = title;
196 + window.title = title.clone();
188 197 }
189 198 }
190 199 }
200 + crate::foreign_toplevel::broadcast_title(state, &data.surface, &title);
191 201 }
192 202 xdg_toplevel::Request::SetAppId { app_id } => {
193 - *data.app_id.lock_or_recover() = app_id;
203 + *data.app_id.lock_or_recover() = app_id.clone();
204 + if let Some(sd) = data.surface.data::<SurfaceData>() {
205 + if let Some(info) = sd.toplevel.lock_or_recover().as_mut() {
206 + info.app_id = app_id.clone();
207 + }
208 + }
209 + crate::foreign_toplevel::broadcast_app_id(state, &data.surface, &app_id);
210 + }
211 + xdg_toplevel::Request::Destroy => {
212 + crate::foreign_toplevel::announce_closed(&data.surface);
194 213 }
195 214 _ => {}
196 215 }
@@ -5,6 +5,7 @@ use std::time::Instant;
5 5
6 6 use libluna_core::{Modifiers, Point, Rect};
7 7 use libluna_render::ImageData;
8 +use wayland_protocols_wlr::foreign_toplevel::v1::server::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1;
8 9 use wayland_server::protocol::wl_data_device::WlDataDevice;
9 10 use wayland_server::protocol::wl_data_source::WlDataSource;
10 11 use wayland_server::protocol::wl_keyboard::WlKeyboard;
@@ -198,6 +199,8 @@ pub struct State {
198 199 z_counter: i32,
199 200 /// Monitory (współdzielone z wątkiem wyjścia).
200 201 pub outputs: Outputs,
202 + /// Podłączeni menedżerowie `zwlr_foreign_toplevel` (paski/taskbary).
203 + pub toplevel_managers: Vec<ZwlrForeignToplevelManagerV1>,
201 204
202 205 // — Wejście —
203 206 pub pointer: Option<WlPointer>,
@@ -233,6 +236,7 @@ impl Default for State {
233 236 slots: Vec::new(),
234 237 z_counter: 1,
235 238 outputs: Arc::new(Mutex::new(vec![OutputInfo::default()])),
239 + toplevel_managers: Vec::new(),
236 240 pointer: None,
237 241 keyboard: None,
238 242 pointer_focus: None,