Commit d85810e
7
files
+361
added
-109
deleted
@@ -7,7 +7,7 @@
7
7
8
8
use libluna_core::{Color, Point, Rect, Scale, Size};
9
9
use tiny_skia::{
10
- FillRule, IntSize, LineCap, LineJoin, Mask, Paint, Path, PathBuilder, Pixmap, PixmapPaint,
10
+ FillRule, LineCap, LineJoin, Mask, Paint, Path, PathBuilder, Pixmap, PixmapPaint, PixmapRef,
11
11
Shader, Stroke, Transform,
12
12
};
13
13
@@ -158,10 +158,9 @@ impl SoftwareRenderer {
158
158
if rect.width() <= 0.0 || rect.height() <= 0.0 {
159
159
continue;
160
160
}
161
- let Some(size) = IntSize::from_wh(data.width, data.height) else {
162
- continue;
163
- };
164
- let Some(src) = Pixmap::from_vec(data.rgba.clone(), size) else {
161
+ // Bez kopiowania: widok na piksele sceny.
162
+ let Some(src) = PixmapRef::from_bytes(&data.rgba, data.width, data.height)
163
+ else {
165
164
continue;
166
165
};
167
166
let k = scale.factor() as f32;
@@ -169,14 +168,7 @@ impl SoftwareRenderer {
169
168
let sy = (rect.height() as f32 / data.height as f32) * k;
170
169
let t = Transform::from_scale(sx, sy)
171
170
.post_translate(rect.x() as f32 * k, rect.y() as f32 * k);
172
- pixmap.draw_pixmap(
173
- 0,
174
- 0,
175
- src.as_ref(),
176
- &PixmapPaint::default(),
177
- t,
178
- mask.as_ref(),
179
- );
171
+ pixmap.draw_pixmap(0, 0, src, &PixmapPaint::default(), t, mask.as_ref());
180
172
}
181
173
}
182
174
}
@@ -3,7 +3,7 @@
3
3
use std::sync::Mutex;
4
4
5
5
use libluna_render::ImageData;
6
-use wayland_protocols::xdg::shell::server::{xdg_surface, xdg_toplevel};
6
+use wayland_protocols::xdg::shell::server::{xdg_popup, xdg_surface, xdg_toplevel};
7
7
use wayland_server::{
8
8
protocol::{wl_buffer, wl_callback, wl_compositor, wl_region, wl_surface},
9
9
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
@@ -138,16 +138,19 @@ impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
138
138
}
139
139
}
140
140
141
-/// Skomponuj powierzchnię do modelu okien, jeśli jest to toplevel z buforem `wl_shm`.
141
+/// Skomponuj powierzchnię do modelu okien (toplevel lub popup z buforem `wl_shm`).
142
142
fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceData) {
143
143
let Some(xdg_surface) = data.xdg_surface.lock().unwrap().clone() else {
144
144
return;
145
145
};
146
- let is_toplevel = xdg_surface
147
- .data::<XdgSurfaceData>()
148
- .map(|d| d.toplevel.lock().unwrap().is_some())
149
- .unwrap_or(false);
150
- if !is_toplevel {
146
+ let (is_toplevel, is_popup) = match xdg_surface.data::<XdgSurfaceData>() {
147
+ Some(d) => (
148
+ d.toplevel.lock().unwrap().is_some(),
149
+ *d.is_popup.lock().unwrap(),
150
+ ),
151
+ None => (false, false),
152
+ };
153
+ if !is_toplevel && !is_popup {
151
154
return;
152
155
}
153
156
let Some(buffer) = data.buffer.lock().unwrap().clone() else {
@@ -161,16 +164,26 @@ fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceD
161
164
return;
162
165
};
163
166
let image = std::sync::Arc::new(ImageData::new(w as u32, h as u32, rgba));
167
+ // Położenie popupu liczymy przed zablokowaniem modelu (odczyt rodzica).
168
+ let popup_xy = if is_popup {
169
+ Some(popup_position(state, &xdg_surface))
170
+ } else {
171
+ None
172
+ };
164
173
let z = state.next_z();
165
- let title = xdg_surface
166
- .data::<XdgSurfaceData>()
167
- .and_then(|d| {
168
- d.toplevel.lock().unwrap().as_ref().and_then(|tl| {
169
- tl.data::<XdgToplevelData>()
170
- .map(|td| td.title.lock().unwrap().clone())
174
+ let title = if is_toplevel {
175
+ xdg_surface
176
+ .data::<XdgSurfaceData>()
177
+ .and_then(|d| {
178
+ d.toplevel.lock().unwrap().as_ref().and_then(|tl| {
179
+ tl.data::<XdgToplevelData>()
180
+ .map(|td| td.title.lock().unwrap().clone())
181
+ })
171
182
})
172
- })
173
- .unwrap_or_default();
183
+ .unwrap_or_default()
184
+ } else {
185
+ String::new()
186
+ };
174
187
175
188
let mut slot = data.slot.lock().unwrap();
176
189
let mut windows = state.windows.lock().unwrap();
@@ -179,13 +192,16 @@ fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceD
179
192
Some(i) => i,
180
193
None => {
181
194
let i = windows.len();
195
+ let (px, py) = popup_xy.unwrap_or((60.0 + i as f64 * 36.0, 60.0 + i as f64 * 36.0));
182
196
windows.push(Window {
183
- x: 60.0 + i as f64 * 36.0,
184
- y: 60.0 + i as f64 * 36.0,
197
+ x: px,
198
+ y: py,
185
199
width: w,
186
200
height: h,
187
201
z,
188
202
title: title.clone(),
203
+ popup: is_popup,
204
+ mapped: true,
189
205
data: image.clone(),
190
206
});
191
207
*slot = Some(i);
@@ -196,6 +212,7 @@ fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceD
196
212
if let Some(window) = windows.get_mut(idx) {
197
213
window.width = w;
198
214
window.height = h;
215
+ window.mapped = true;
199
216
if !title.is_empty() {
200
217
window.title = title;
201
218
}
@@ -207,11 +224,66 @@ fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceD
207
224
if created && state.slots.len() == idx {
208
225
state.slots.push(surface.clone());
209
226
}
227
+ // Popup: wyślij konfigurację (rozmiar + pozycja).
228
+ if is_popup && created {
229
+ send_popup_configure(state, &xdg_surface, popup_xy.unwrap_or((0.0, 0.0)), w, h);
230
+ }
210
231
// Pozwól klientowi ponownie użyć bufora.
211
232
let _ = buffer.send_event(wl_buffer::Event::Release);
212
233
}
213
234
214
-/// Jeśli powierzchnia ma `xdg_surface` jeszcze nie skonfigurowany, wyślij configure.
235
+/// Pozycja popupu względem treści rodzica (prostokąt kotwicy + offset).
236
+fn popup_position(state: &State, xdg_surface: &xdg_surface::XdgSurface) -> (f64, f64) {
237
+ let Some(d) = xdg_surface.data::<XdgSurfaceData>() else {
238
+ return (0.0, 0.0);
239
+ };
240
+ let geom = *d.geom.lock().unwrap();
241
+ let parent_origin = d
242
+ .parent
243
+ .lock()
244
+ .unwrap()
245
+ .clone()
246
+ .and_then(|ps| ps.data::<SurfaceData>().map(|sd| *sd.slot.lock().unwrap()))
247
+ .flatten()
248
+ .and_then(|slot| {
249
+ state.windows.lock().unwrap().get(slot).map(|w| {
250
+ let c = w.content();
251
+ (c.x(), c.y())
252
+ })
253
+ })
254
+ .unwrap_or((0.0, 0.0));
255
+ (
256
+ parent_origin.0 + geom.anchor_x + geom.offset_x,
257
+ parent_origin.1 + geom.anchor_y + geom.anchor_h + geom.offset_y,
258
+ )
259
+}
260
+
261
+/// Wysyła `xdg_popup.configure` + `xdg_surface.configure` (raz).
262
+fn send_popup_configure(
263
+ state: &mut State,
264
+ xdg_surface: &xdg_surface::XdgSurface,
265
+ (x, y): (f64, f64),
266
+ w: i32,
267
+ h: i32,
268
+) {
269
+ let serial = state.next_serial();
270
+ if let Some(d) = xdg_surface.data::<XdgSurfaceData>() {
271
+ if let Some(popup) = d.popup.lock().unwrap().as_ref() {
272
+ let _ = popup.send_event(xdg_popup::Event::Configure {
273
+ x: x as i32,
274
+ y: y as i32,
275
+ width: w,
276
+ height: h,
277
+ });
278
+ }
279
+ if !*d.configured.lock().unwrap() {
280
+ let _ = xdg_surface.send_event(xdg_surface::Event::Configure { serial });
281
+ *d.configured.lock().unwrap() = true;
282
+ }
283
+ }
284
+}
285
+
286
+/// Jeśli toplevel nie ma jeszcze konfiguracji, wyślij `configure` (popupy mają własną).
215
287
fn maybe_configure_xdg(state: &mut State, data: &SurfaceData) {
216
288
let Some(xdg_surface) = data.xdg_surface.lock().unwrap().clone() else {
217
289
return;
@@ -219,6 +291,9 @@ fn maybe_configure_xdg(state: &mut State, data: &SurfaceData) {
219
291
let Some(xdg_data) = xdg_surface.data::<XdgSurfaceData>() else {
220
292
return;
221
293
};
294
+ if *xdg_data.is_popup.lock().unwrap() {
295
+ return;
296
+ }
222
297
if *xdg_data.configured.lock().unwrap() {
223
298
return;
224
299
}
@@ -60,44 +60,49 @@ fn draw(frame: &mut Frame, model: &WindowModel) {
60
60
let mut order: Vec<&Window> = windows.iter().collect();
61
61
order.sort_by_key(|w| w.z);
62
62
for w in order {
63
+ if !w.mapped {
64
+ continue;
65
+ }
63
66
// Tło treści (na wypadek przezroczystości bufora klienta).
64
67
frame
65
68
.scene
66
69
.fill_rect(w.content(), Color::from_rgba8(0x00, 0x00, 0x00, 0xff));
67
70
// Treść okna (bufor klienta).
68
71
frame.scene.image(w.content(), w.data.clone());
69
- // Pasek tytułu (dekoracja po stronie serwera).
70
- frame
71
- .scene
72
- .fill_rect(w.titlebar(), Color::from_rgba8(0x2c, 0x2f, 0x38, 0xff));
73
- frame.scene.text(
74
- TextRun::new(Point::new(w.x + 10.0, w.y + 20.0), &w.title, 13.0)
75
- .with_color(Color::from_rgba8(0xee, 0xee, 0xee, 0xff)),
76
- );
77
- // Przycisk zamknięcia (krzyżyk).
78
- let c = w.close_rect().center();
79
- let glyph = Color::from_rgba8(0xd0, 0xd0, 0xd0, 0xff);
80
- frame.scene.stroke_line(
81
- Point::new(c.x - 4.0, c.y - 4.0),
82
- Point::new(c.x + 4.0, c.y + 4.0),
83
- 1.6,
84
- glyph,
85
- );
86
- frame.scene.stroke_line(
87
- Point::new(c.x + 4.0, c.y - 4.0),
88
- Point::new(c.x - 4.0, c.y + 4.0),
89
- 1.6,
90
- glyph,
91
- );
92
- // Uchwyt przeskalowania (3 kropki w narożniku).
93
- let g = w.grip_rect();
94
- for i in 0..3 {
95
- let x = g.right() - 3.0 - i as f64 * 4.0;
96
- let y = g.bottom() - 3.0 - i as f64 * 4.0;
97
- frame.scene.fill_rect(
98
- Rect::from_xywh(x, y, 2.0, 2.0),
99
- Color::from_rgba8(0x9a, 0x9e, 0xa8, 0xff),
72
+ // Dekoracja tylko dla topleveli (popupy jej nie mają).
73
+ if !w.popup {
74
+ frame
75
+ .scene
76
+ .fill_rect(w.titlebar(), Color::from_rgba8(0x2c, 0x2f, 0x38, 0xff));
77
+ frame.scene.text(
78
+ TextRun::new(Point::new(w.x + 10.0, w.y + 20.0), &w.title, 13.0)
79
+ .with_color(Color::from_rgba8(0xee, 0xee, 0xee, 0xff)),
80
+ );
81
+ // Przycisk zamknięcia (krzyżyk).
82
+ let c = w.close_rect().center();
83
+ let glyph = Color::from_rgba8(0xd0, 0xd0, 0xd0, 0xff);
84
+ frame.scene.stroke_line(
85
+ Point::new(c.x - 4.0, c.y - 4.0),
86
+ Point::new(c.x + 4.0, c.y + 4.0),
87
+ 1.6,
88
+ glyph,
100
89
);
90
+ frame.scene.stroke_line(
91
+ Point::new(c.x + 4.0, c.y - 4.0),
92
+ Point::new(c.x - 4.0, c.y + 4.0),
93
+ 1.6,
94
+ glyph,
95
+ );
96
+ // Uchwyt przeskalowania (3 kropki w narożniku).
97
+ let g = w.grip_rect();
98
+ for i in 0..3 {
99
+ let x = g.right() - 3.0 - i as f64 * 4.0;
100
+ let y = g.bottom() - 3.0 - i as f64 * 4.0;
101
+ frame.scene.fill_rect(
102
+ Rect::from_xywh(x, y, 2.0, 2.0),
103
+ Color::from_rgba8(0x9a, 0x9e, 0xa8, 0xff),
104
+ );
105
+ }
101
106
}
102
107
// Obramowanie całego okna.
103
108
frame.scene.stroke_rounded_rect(
@@ -9,7 +9,7 @@ use std::io::{Seek, Write};
9
9
use std::os::fd::{AsFd, OwnedFd};
10
10
11
11
use libluna_core::{ButtonState, Event, Modifiers, Point, PointerButton};
12
-use wayland_protocols::xdg::shell::server::{xdg_surface, xdg_toplevel};
12
+use wayland_protocols::xdg::shell::server::{xdg_popup, xdg_surface, xdg_toplevel};
13
13
use wayland_server::protocol::{wl_keyboard, wl_pointer, wl_seat, wl_touch};
14
14
use wayland_server::{
15
15
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
@@ -143,7 +143,7 @@ fn hit(state: &State, pos: Point) -> Option<(usize, Part)> {
143
143
let windows = state.windows.lock().unwrap();
144
144
let mut best: Option<(usize, i32)> = None;
145
145
for (i, w) in windows.iter().enumerate() {
146
- if w.outer().contains(pos) {
146
+ if w.mapped && w.outer().contains(pos) {
147
147
match best {
148
148
Some((_, z)) if z >= w.z => {}
149
149
_ => best = Some((i, w.z)),
@@ -169,7 +169,7 @@ fn content_window(state: &State, pos: Point) -> Option<usize> {
169
169
let windows = state.windows.lock().unwrap();
170
170
let mut best: Option<(usize, i32)> = None;
171
171
for (i, w) in windows.iter().enumerate() {
172
- if w.content().contains(pos) {
172
+ if w.mapped && w.content().contains(pos) {
173
173
match best {
174
174
Some((_, z)) if z >= w.z => {}
175
175
_ => best = Some((i, w.z)),
@@ -179,6 +179,50 @@ fn content_window(state: &State, pos: Point) -> Option<usize> {
179
179
best.map(|(i, _)| i)
180
180
}
181
181
182
+/// Zamyka popupy, poza którymi kliknięto (wysyła `xdg_popup.popup_done`, zdejmuje z widoku).
183
+fn dismiss_popups(state: &mut State, pos: Point) {
184
+ loop {
185
+ let top = {
186
+ let windows = state.windows.lock().unwrap();
187
+ let mut best: Option<(usize, i32)> = None;
188
+ for (i, w) in windows.iter().enumerate() {
189
+ if w.popup && w.mapped {
190
+ match best {
191
+ Some((_, z)) if z >= w.z => {}
192
+ _ => best = Some((i, w.z)),
193
+ }
194
+ }
195
+ }
196
+ best.map(|(i, _)| i)
197
+ };
198
+ let Some(slot) = top else { break };
199
+ let inside = state
200
+ .windows
201
+ .lock()
202
+ .unwrap()
203
+ .get(slot)
204
+ .map(|w| w.outer().contains(pos))
205
+ .unwrap_or(false);
206
+ if inside {
207
+ break;
208
+ }
209
+ if let Some(surface) = state.slots.get(slot).cloned() {
210
+ if let Some(sd) = surface.data::<SurfaceData>() {
211
+ if let Some(xs) = sd.xdg_surface.lock().unwrap().clone() {
212
+ if let Some(d) = xs.data::<XdgSurfaceData>() {
213
+ if let Some(popup) = d.popup.lock().unwrap().as_ref() {
214
+ let _ = popup.send_event(xdg_popup::Event::PopupDone);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+ if let Some(w) = state.windows.lock().unwrap().get_mut(slot) {
221
+ w.mapped = false;
222
+ }
223
+ }
224
+}
225
+
182
226
fn window_geometry(state: &State, slot: usize) -> Option<(f64, f64, f64, f64)> {
183
227
let windows = state.windows.lock().unwrap();
184
228
windows
@@ -224,6 +268,9 @@ fn pointer_button(state: &mut State, pos: Point, button: PointerButton, st: Butt
224
268
if st == ButtonState::Released {
225
269
state.drag = None;
226
270
state.resize = None;
271
+ } else {
272
+ // Klik poza popupem zamyka popup (przed trafieniem w okno).
273
+ dismiss_popups(state, pos);
227
274
}
228
275
229
276
// Obsłuż dekorację/okno pod kursorem.
@@ -654,6 +701,8 @@ mod tests {
654
701
height: 120,
655
702
z: 1,
656
703
title: "Test".to_owned(),
704
+ popup: false,
705
+ mapped: true,
657
706
data: Arc::new(ImageData::empty(200, 120)),
658
707
}
659
708
}
@@ -3,7 +3,7 @@
3
3
use std::sync::Mutex;
4
4
5
5
use wayland_protocols::xdg::shell::server::{
6
- xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
6
+ xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
7
7
};
8
8
use wayland_server::{
9
9
protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
@@ -13,13 +13,40 @@ use wayland_server::{
13
13
use crate::compositor::SurfaceData;
14
14
use crate::state::State;
15
15
16
+/// Geometria popupu zebrana z `xdg_positioner` (uproszczona: prostokąt kotwicy + offset).
17
+#[derive(Debug, Clone, Copy, Default)]
18
+pub struct PopupGeom {
19
+ pub width: i32,
20
+ pub height: i32,
21
+ pub anchor_x: f64,
22
+ pub anchor_y: f64,
23
+ pub anchor_w: f64,
24
+ pub anchor_h: f64,
25
+ pub offset_x: f64,
26
+ pub offset_y: f64,
27
+}
28
+
29
+/// Dane `xdg_positioner`.
30
+#[derive(Debug, Default)]
31
+pub struct XdgPositionerData {
32
+ pub geom: Mutex<PopupGeom>,
33
+}
34
+
16
35
/// Dane `xdg_surface`.
17
-#[allow(dead_code)] // `wl_surface` przyda się przy renderowaniu/geometrii w kolejnych krokach
36
+#[allow(dead_code)]
18
37
pub struct XdgSurfaceData {
19
38
/// Powiązana powierzchnia `wl_surface`.
20
39
pub wl_surface: WlSurface,
21
40
/// Utworzona powierzchnia najwyższego poziomu (jeśli `get_toplevel`).
22
41
pub toplevel: Mutex<Option<xdg_toplevel::XdgToplevel>>,
42
+ /// Utworzony popup (jeśli `get_popup`).
43
+ pub popup: Mutex<Option<xdg_popup::XdgPopup>>,
44
+ /// Powierzchnia rodzica (dla popupu).
45
+ pub parent: Mutex<Option<WlSurface>>,
46
+ /// Geometria popupu z positionera.
47
+ pub geom: Mutex<PopupGeom>,
48
+ /// Czy to popup.
49
+ pub is_popup: Mutex<bool>,
23
50
/// Czy wysłano już pierwszą konfigurację.
24
51
pub configured: Mutex<bool>,
25
52
}
@@ -29,6 +56,10 @@ impl XdgSurfaceData {
29
56
Self {
30
57
wl_surface,
31
58
toplevel: Mutex::new(None),
59
+ popup: Mutex::new(None),
60
+ parent: Mutex::new(None),
61
+ geom: Mutex::new(PopupGeom::default()),
62
+ is_popup: Mutex::new(false),
32
63
configured: Mutex::new(false),
33
64
}
34
65
}
@@ -77,7 +108,7 @@ impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
77
108
) {
78
109
match request {
79
110
xdg_wm_base::Request::CreatePositioner { id } => {
80
- data_init.init(id, ());
111
+ data_init.init(id, XdgPositionerData::default());
81
112
}
82
113
xdg_wm_base::Request::GetXdgSurface { id, surface } => {
83
114
let xdg = data_init.init(id, XdgSurfaceData::new(surface.clone()));
@@ -106,6 +137,23 @@ impl Dispatch<xdg_surface::XdgSurface, XdgSurfaceData> for State {
106
137
let toplevel = data_init.init(id, XdgToplevelData::new(data.wl_surface.clone()));
107
138
*data.toplevel.lock().unwrap() = Some(toplevel);
108
139
}
140
+ xdg_surface::Request::GetPopup {
141
+ id,
142
+ parent,
143
+ positioner,
144
+ } => {
145
+ let popup = data_init.init(id, ());
146
+ *data.popup.lock().unwrap() = Some(popup);
147
+ *data.is_popup.lock().unwrap() = true;
148
+ *data.parent.lock().unwrap() = parent
149
+ .as_ref()
150
+ .and_then(|p| p.data::<XdgSurfaceData>())
151
+ .map(|p| p.wl_surface.clone());
152
+ *data.geom.lock().unwrap() = positioner
153
+ .data::<XdgPositionerData>()
154
+ .map(|p| *p.geom.lock().unwrap())
155
+ .unwrap_or_default();
156
+ }
109
157
xdg_surface::Request::AckConfigure { .. } => {
110
158
// Konfiguracja potwierdzona — nic więcej nie trzeba po stronie MVP.
111
159
}
@@ -145,12 +193,48 @@ impl Dispatch<xdg_toplevel::XdgToplevel, XdgToplevelData> for State {
145
193
}
146
194
}
147
195
148
-impl Dispatch<xdg_positioner::XdgPositioner, ()> for State {
196
+impl Dispatch<xdg_positioner::XdgPositioner, XdgPositionerData> for State {
149
197
fn request(
150
198
_state: &mut Self,
151
199
_client: &Client,
152
200
_resource: &xdg_positioner::XdgPositioner,
153
- _request: xdg_positioner::Request,
201
+ request: xdg_positioner::Request,
202
+ data: &XdgPositionerData,
203
+ _handle: &DisplayHandle,
204
+ _data_init: &mut DataInit<'_, Self>,
205
+ ) {
206
+ let mut geom = data.geom.lock().unwrap();
207
+ match request {
208
+ xdg_positioner::Request::SetSize { width, height } => {
209
+ geom.width = width;
210
+ geom.height = height;
211
+ }
212
+ xdg_positioner::Request::SetAnchorRect {
213
+ x,
214
+ y,
215
+ width,
216
+ height,
217
+ } => {
218
+ geom.anchor_x = x as f64;
219
+ geom.anchor_y = y as f64;
220
+ geom.anchor_w = width as f64;
221
+ geom.anchor_h = height as f64;
222
+ }
223
+ xdg_positioner::Request::SetOffset { x, y } => {
224
+ geom.offset_x = x as f64;
225
+ geom.offset_y = y as f64;
226
+ }
227
+ _ => {}
228
+ }
229
+ }
230
+}
231
+
232
+impl Dispatch<xdg_popup::XdgPopup, ()> for State {
233
+ fn request(
234
+ _state: &mut Self,
235
+ _client: &Client,
236
+ _resource: &xdg_popup::XdgPopup,
237
+ _request: xdg_popup::Request,
154
238
_data: &(),
155
239
_handle: &DisplayHandle,
156
240
_data_init: &mut DataInit<'_, Self>,
@@ -11,10 +11,63 @@ use wayland_server::{
11
11
12
12
use crate::state::State;
13
13
14
-/// Dane puli pamięci (`wl_shm_pool`): deskryptor + rozmiar. Współdzielone przez bufory.
14
+/// Dane puli pamięci (`wl_shm_pool`): deskryptor, rozmiar i **zbuforowana mapa**.
15
+///
16
+/// Mapę tworzymy raz (przy `create_pool`) i zwalniamy przy zniszczeniu puli — zamiast
17
+/// mapować/odmapowywać przy każdym commicie.
15
18
pub struct ShmPoolData {
19
+ #[allow(dead_code)]
16
20
pub fd: OwnedFd,
17
21
pub size: usize,
22
+ map: *mut c_void,
23
+}
24
+
25
+// Mapa jest tylko do odczytu i współdzielona — bezpieczna do wysyłania między wątkami.
26
+unsafe impl Send for ShmPoolData {}
27
+unsafe impl Sync for ShmPoolData {}
28
+
29
+impl ShmPoolData {
30
+ /// Mapuje pulę (tylko odczyt). Przy niepowodzeniu zostaje pusta (bez paniki).
31
+ pub fn new(fd: OwnedFd, size: usize) -> Self {
32
+ let map = if size == 0 {
33
+ std::ptr::null_mut()
34
+ } else {
35
+ // SAFETY: `fd` żyje w strukturze, mapujemy tylko do odczytu.
36
+ unsafe {
37
+ rustix::mm::mmap(
38
+ std::ptr::null_mut::<c_void>(),
39
+ size,
40
+ rustix::mm::ProtFlags::READ,
41
+ rustix::mm::MapFlags::PRIVATE,
42
+ &fd,
43
+ 0,
44
+ )
45
+ }
46
+ .unwrap_or(std::ptr::null_mut())
47
+ };
48
+ Self { fd, size, map }
49
+ }
50
+
51
+ /// Bajty puli (widok tylko do odczytu; pusty, gdy mapowanie się nie powiodło).
52
+ pub fn bytes(&self) -> &[u8] {
53
+ if self.map.is_null() {
54
+ return &[];
55
+ }
56
+ // SAFETY: `map` wskazuje `size` bajtów zamapowanej pamięci.
57
+ unsafe { std::slice::from_raw_parts(self.map as *const u8, self.size) }
58
+ }
59
+}
60
+
61
+impl Drop for ShmPoolData {
62
+ fn drop(&mut self) {
63
+ if self.map.is_null() {
64
+ return;
65
+ }
66
+ // SAFETY: odmapowujemy ten sam zakres, który zmapowaliśmy.
67
+ unsafe {
68
+ let _ = rustix::mm::munmap(self.map, self.size);
69
+ }
70
+ }
18
71
}
19
72
20
73
/// Dane bufora (`wl_buffer`): odniesienie do puli + geometria.
@@ -29,37 +82,22 @@ pub struct BufferData {
29
82
30
83
impl BufferData {
31
84
/// Odczytuje piksele bufora jako premultiplied RGBA8 (swizzle BGRA→RGBA).
32
- ///
33
- /// Mapujemy pulę `wl_shm` tylko na czas odczytu (MVP; docelowo mapa cache'owana).
34
85
pub fn to_rgba(&self) -> Option<Vec<u8>> {
35
86
if self.width <= 0 || self.height <= 0 || self.stride <= 0 {
36
87
return None;
37
88
}
38
- let len = self.pool.size;
39
- // SAFETY: mapujemy tylko do odczytu, na czas wywołania; `fd` żyje w puli.
40
- let map = unsafe {
41
- rustix::mm::mmap(
42
- std::ptr::null_mut::<c_void>(),
43
- len,
44
- rustix::mm::ProtFlags::READ,
45
- rustix::mm::MapFlags::PRIVATE,
46
- &self.pool.fd,
47
- 0,
48
- )
49
- }
50
- .ok()?;
51
- let bytes = unsafe { std::slice::from_raw_parts(map as *const u8, len) };
52
-
53
89
let (w, h) = (self.width as usize, self.height as usize);
54
90
let stride = self.stride as usize;
91
+ // Zakres musi mieścić się w puli.
92
+ if self.offset + stride * h > self.pool.size {
93
+ return None;
94
+ }
95
+ let bytes = self.pool.bytes();
55
96
let mut out = vec![0u8; w * h * 4];
56
97
for y in 0..h {
57
98
let row = self.offset + y * stride;
58
99
for x in 0..w {
59
100
let i = row + x * 4;
60
- if i + 4 > bytes.len() {
61
- break;
62
- }
63
101
let o = (y * w + x) * 4;
64
102
// ARGB8888 (pamięć: B,G,R,A) → RGBA8; XRGB8888 → alfa wymuszona na 1.
65
103
out[o] = bytes[i + 2];
@@ -72,10 +110,6 @@ impl BufferData {
72
110
};
73
111
}
74
112
}
75
- // SAFETY: odmapowujemy dokładnie ten sam zakres.
76
- unsafe {
77
- let _ = rustix::mm::munmap(map, len);
78
- }
79
113
Some(out)
80
114
}
81
115
}
@@ -108,13 +142,7 @@ impl Dispatch<wl_shm::WlShm, ()> for State {
108
142
data_init: &mut DataInit<'_, Self>,
109
143
) {
110
144
if let wl_shm::Request::CreatePool { id, fd, size } = request {
111
- data_init.init(
112
- id,
113
- Arc::new(ShmPoolData {
114
- fd,
115
- size: size.max(0) as usize,
116
- }),
117
- );
145
+ data_init.init(id, Arc::new(ShmPoolData::new(fd, size.max(0) as usize)));
118
146
}
119
147
}
120
148
}
@@ -16,7 +16,7 @@ pub const TITLEBAR: f64 = 30.0;
16
16
/// Bok uchwytu przeskalowania w prawym-dolnym narożniku.
17
17
pub const GRIP: f64 = 16.0;
18
18
19
-/// Okno (toplevel) w modelu kompozycji: pozycja, rozmiar, kolejność i gotowe piksele.
19
+/// Okno (toplevel/popup) w modelu kompozycji: pozycja, rozmiar, kolejność i piksele.
20
20
pub struct Window {
21
21
pub x: f64,
22
22
pub y: f64,
@@ -27,38 +27,54 @@ pub struct Window {
27
27
pub z: i32,
28
28
/// Tytuł okna (z `xdg_toplevel.set_title`).
29
29
pub title: String,
30
+ /// Czy to `xdg_popup` (bez dekoracji, zamykany kliknięciem poza).
31
+ pub popup: bool,
32
+ /// Czy powierzchnia jest widoczna (popup znika po `popup_done`).
33
+ pub mapped: bool,
30
34
/// Piksele (premultiplied RGBA8) ostatnio zacommitowanego bufora.
31
35
pub data: Arc<ImageData>,
32
36
}
33
37
34
38
impl Window {
39
+ /// Wysokość dekoracji (zero dla popupów).
40
+ fn bar(&self) -> f64 {
41
+ if self.popup {
42
+ 0.0
43
+ } else {
44
+ TITLEBAR
45
+ }
46
+ }
47
+
35
48
/// Prostokąt całego okna (dekoracja + treść).
36
49
pub fn outer(&self) -> Rect {
37
50
Rect::from_xywh(
38
51
self.x,
39
52
self.y,
40
53
self.width as f64,
41
- TITLEBAR + self.height as f64,
54
+ self.bar() + self.height as f64,
42
55
)
43
56
}
44
57
45
- /// Pasek tytułu (górna część dekoracji).
58
+ /// Pasek tytułu (zerowy dla popupów).
46
59
pub fn titlebar(&self) -> Rect {
47
- Rect::from_xywh(self.x, self.y, self.width as f64, TITLEBAR)
60
+ Rect::from_xywh(self.x, self.y, self.width as f64, self.bar())
48
61
}
49
62
50
63
/// Obszar treści (klienta).
51
64
pub fn content(&self) -> Rect {
52
65
Rect::from_xywh(
53
66
self.x,
54
- self.y + TITLEBAR,
67
+ self.y + self.bar(),
55
68
self.width as f64,
56
69
self.height as f64,
57
70
)
58
71
}
59
72
60
- /// Kwadratowy przycisk zamknięcia w prawym górnym narożniku.
73
+ /// Kwadratowy przycisk zamknięcia (tylko dla topleveli).
61
74
pub fn close_rect(&self) -> Rect {
75
+ if self.popup {
76
+ return Rect::ZERO;
77
+ }
62
78
Rect::from_xywh(
63
79
self.x + self.width as f64 - TITLEBAR,
64
80
self.y,
@@ -67,8 +83,11 @@ impl Window {
67
83
)
68
84
}
69
85
70
- /// Uchwyt przeskalowania (prawy-dolny narożnik treści).
86
+ /// Uchwyt przeskalowania (prawy-dolny narożnik treści; tylko dla topleveli).
71
87
pub fn grip_rect(&self) -> Rect {
88
+ if self.popup {
89
+ return Rect::ZERO;
90
+ }
72
91
let c = self.content();
73
92
Rect::from_xywh(c.right() - GRIP, c.bottom() - GRIP, GRIP, GRIP)
74
93
}