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,