← luna

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 }