← luna

Commit dcbc4d6

13
files
+328
added
-36
deleted
@@ -450,6 +450,7 @@ version = "0.0.1"
450 450 dependencies = [
451 451 "libluna-core",
452 452 "libluna-i18n",
453 + "libluna-platform",
453 454 "libluna-render",
454 455 "rustix 0.38.44",
455 456 "wayland-protocols",
@@ -320,8 +320,9 @@ XDG_CONFIG_HOME="$PWD/examples" cargo run -p luna-demo --bin render -- --lang fr
320 320 - `libluna-i18n`: `Locale` (z `LANG`/`LC_*`), `Catalog`, `Translations`, globalne `tr`/`set_locale`,
321 321 makro `t!` z podstawianiem argumentów, **języki z osobnych plików** `i18n/<kod>.lang`
322 322 (wbudowane) oraz **doładowywane w runtime** z katalogów; `languages()` i `language_name()`.
323 -- `libluna-render`: graf sceny (prostokąty, zaokrąglenia, obramowania, linie, clip, tekst)
324 - i rasteryzator CPU z antyaliasingiem; tekst przez `fontdue` (font Adwaita Sans).
323 +- `libluna-render`: graf sceny (prostokąty, zaokrąglenia, obramowania, linie, clip, tekst,
324 + **obrazy** `Scene::image`/`ImageData`) i rasteryzator CPU z antyaliasingiem; tekst przez
325 + `fontdue` (font Adwaita Sans).
325 326 - `libluna-platform`: okno `xdg-shell` z CSD, bufor `wl_shm`, pętla `calloop`, **zdarzenia
326 327 wejścia** (wskaźnik, klawiatura, scroll) w `Frame`, odświeżanie na żądanie, zarządzanie
327 328 motywem przez `Frame::set_preference` / `set_accent` (zapis + natychmiastowe przerysowanie).
@@ -17,6 +17,6 @@ pub mod scene;
17 17 pub mod software;
18 18 pub mod text;
19 19
20 -pub use scene::{DrawCommand, Scene, TextRun};
20 +pub use scene::{DrawCommand, ImageData, Scene, TextRun};
21 21 pub use software::SoftwareRenderer;
22 22 pub use text::TextEngine;
@@ -5,6 +5,35 @@
5 5
6 6 use libluna_core::style::weight;
7 7 use libluna_core::{Color, Point, Rect};
8 +use std::sync::Arc;
9 +
10 +/// Gotowy obraz (premultiplied RGBA8) — np. skomponowany bufor okna z `wl_shm`.
11 +#[derive(Debug, Clone, PartialEq)]
12 +pub struct ImageData {
13 + pub width: u32,
14 + pub height: u32,
15 + /// Piksele premultiplied RGBA8, dokładnie `width * height * 4` bajtów.
16 + pub rgba: Vec<u8>,
17 +}
18 +
19 +impl ImageData {
20 + pub fn new(width: u32, height: u32, rgba: Vec<u8>) -> Self {
21 + Self {
22 + width,
23 + height,
24 + rgba,
25 + }
26 + }
27 +
28 + /// Pusty (przezroczysty) obraz o zadanym rozmiarze.
29 + pub fn empty(width: u32, height: u32) -> Self {
30 + Self {
31 + width,
32 + height,
33 + rgba: vec![0; width as usize * height as usize * 4],
34 + }
35 + }
36 +}
8 37
9 38 /// Pojedynczy fragment tekstu do narysowania.
10 39 #[derive(Debug, Clone, PartialEq)]
@@ -72,6 +101,8 @@ pub enum DrawCommand {
72 101 PopClip,
73 102 /// Tekst.
74 103 Text(TextRun),
104 + /// Obraz (np. bufor okna) wpisany w prostokąt docelowy.
105 + Image { rect: Rect, data: Arc<ImageData> },
75 106 }
76 107
77 108 /// Uporządkowana lista poleceń rysowania.
@@ -158,6 +189,12 @@ impl Scene {
158 189 self.commands.push(DrawCommand::Text(run));
159 190 self
160 191 }
192 +
193 + /// Rysuje obraz wpisany w `rect` (skalowany, jeśli rozmiary się różnią).
194 + pub fn image(&mut self, rect: Rect, data: Arc<ImageData>) -> &mut Self {
195 + self.commands.push(DrawCommand::Image { rect, data });
196 + self
197 + }
161 198 }
162 199
163 200 #[cfg(test)]
@@ -7,7 +7,8 @@
7 7
8 8 use libluna_core::{Color, Point, Rect, Scale, Size};
9 9 use tiny_skia::{
10 - FillRule, LineCap, LineJoin, Mask, Paint, Path, PathBuilder, Pixmap, Shader, Stroke, Transform,
10 + FillRule, IntSize, LineCap, LineJoin, Mask, Paint, Path, PathBuilder, Pixmap, PixmapPaint,
11 + Shader, Stroke, Transform,
11 12 };
12 13
13 14 use crate::scene::{DrawCommand, Scene};
@@ -153,6 +154,30 @@ impl SoftwareRenderer {
153 154 scale.factor(),
154 155 );
155 156 }
157 + DrawCommand::Image { rect, data } => {
158 + if rect.width() <= 0.0 || rect.height() <= 0.0 {
159 + continue;
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 {
165 + continue;
166 + };
167 + let k = scale.factor() as f32;
168 + let sx = (rect.width() as f32 / data.width as f32) * k;
169 + let sy = (rect.height() as f32 / data.height as f32) * k;
170 + let t = Transform::from_scale(sx, sy)
171 + .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 + );
180 + }
156 181 }
157 182 }
158 183 }
@@ -325,4 +350,24 @@ mod tests {
325 350 // Punkt nieużywany wprost — upewnia się, że typ jest w zasięgu.
326 351 let _ = Point::ZERO;
327 352 }
353 +
354 + #[test]
355 + fn draws_image_into_rect() {
356 + use crate::scene::ImageData;
357 + use std::sync::Arc;
358 + let mut r = SoftwareRenderer::new(20, 20);
359 + // Czerwony obraz 2×2 wpisany w prostokąt 10×10 w (5,5).
360 + let data = ImageData::new(
361 + 2,
362 + 2,
363 + vec![
364 + 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255,
365 + ],
366 + );
367 + let mut scene = Scene::new();
368 + scene.image(Rect::from_xywh(5.0, 5.0, 10.0, 10.0), Arc::new(data));
369 + r.draw(&scene, 1.0, Color::WHITE);
370 + assert_eq!(pixel(&r, 10, 10), [255, 0, 0, 255]);
371 + assert_eq!(pixel(&r, 1, 1), [255, 255, 255, 255]);
372 + }
328 373 }
@@ -19,25 +19,31 @@ gnieździe nasłuchu i deskryptorze backendu. `libLuna` jest zwykłą zależnoś
19 19 | Środowisko | **Luna** | pulpit (panel, launcher, ustawienia) — `de/luna` (wkrótce) |
20 20 | Kompozytor | **Selene** | serwer Wayland — `de/selene` |
21 21
22 -## Co już działa / What works (M-K0)
22 +## Co już działa / What works (M-K1)
23 23
24 24 - Globalne interfejsy: `wl_compositor` (v4), `wl_shm`, `xdg_wm_base` (v1), `wl_seat` (v1).
25 25 - Cykl życia powierzchni: `wl_surface` (`attach`/`damage`/`frame`/`commit`), `wl_callback`
26 26 (wywołanie `done` przy commicie), `wl_region`.
27 -- `wl_shm`: `create_pool` → `wl_shm_pool.create_buffer` → `wl_buffer` (metadane bufora).
27 +- `wl_shm`: `create_pool` → `wl_shm_pool.create_buffer` → `wl_buffer`; piksele bufora są
28 + odczytywane (`mmap` puli, swizzle BGRA→RGBA) i wstawiane do modelu okien.
28 29 - xdg-shell: `get_xdg_surface` → `get_toplevel`, pierwszy `commit` wysyła `configure`
29 30 (oraz `xdg_toplevel.configure`), `ack_configure`, `set_title`/`set_app_id`.
31 +- **Wyjście nested**: `libluna-platform` otwiera okno na nadrzędnym kompozytorze (osobny
32 + wątek), a `libluna-render` komponuje scenę (tło + okna klientów + etykieta). `libluna-render`
33 + ma teraz prymityw `Scene::image` (obrazy/ piksele) i `ImageData`.
30 34 - Minimalny `wl_seat` (obiekty wejścia tworzone, bez zdarzeń).
31 35 - Wielojęzyczność: lokalizacja z otoczenia + `i18n/` ładowane w runtime (`libluna-i18n`).
32 36 - Optymalizacja: `[profile.release]` z pełnym LTO i 1 jednostką kodegenu.
33 37
34 38 ## Jeszcze nie / Not yet
35 39
36 -- **Wyjście/renderowanie**: integracja `libluna-render` i backend wyświetlania
37 - (nested Wayland lub DRM/KMS). Dziś kompozytor przyjmuje klientów, ale nie pokazuje okien.
38 -- **Wejście**: zdarzenia klawiatury/wskaźnika (`libinput`/evdev) i `wl_pointer`/`wl_keyboard`.
39 -- Protokoły dodatkowe: `xdg_popup`, `wl_output`, `layer-shell`, `xdg-decoration`, clipboard.
40 -- Zarządzanie oknami (układ, fokus, warstwy) — to warstwa **Luna** (`de/luna`).
40 +- **Wejście**: zdarzenia klawiatury/wskaźnika (`libinput`/evdev) i `wl_pointer`/`wl_keyboard`;
41 + dziś okna są widoczne, ale nieinteraktywne.
42 +- **Zarządzanie oknami**: pozycjonowanie jest tymczasowe (kaskada), brak fokusu/warstw/układu.
43 +- Protokoły dodatkowe: `xdg_popup`, `wl_output`, `layer-shell`, `xdg-decoration`, schowek.
44 +- Optymalizacje: mapa `wl_shm` cache'owana (dziś `mmap` na commit), unikanie klonowania
45 + pikseli na klatkę (cache `ImageData`), render tylko przy zmianie modelu.
46 +- Backend sprzętowy: DRM/KMS + GLES (dziś: software + nested).
41 47
42 48 ## Uruchomienie / Running
43 49
@@ -56,9 +62,10 @@ katalog (renderowanie pojawi się, gdy wejdzie backend wyświetlania).
56 62 ```
57 63 de/selene/src/
58 64 main.rs # display, globale, gniazdo, pętla poll
59 - state.rs # stan współdzielony (czasy, seriale, liczniki)
60 - compositor.rs # wl_compositor / wl_surface / wl_region / wl_callback
61 - shm.rs # wl_shm / wl_shm_pool / wl_buffer
65 + state.rs # stan współdzielony + model okien (współdzielony z wyjściem)
66 + compositor.rs # wl_compositor / wl_surface / wl_region / wl_callback + kompozycja
67 + shm.rs # wl_shm / wl_shm_pool / wl_buffer (+ odczyt pikseli)
62 68 shell.rs # xdg_wm_base / xdg_surface / xdg_toplevel / xdg_positioner
63 69 seat.rs # wl_seat (minimalnie)
70 + output.rs # wyjście nested (libluna-platform + libluna-render)
64 71 ```
@@ -15,7 +15,8 @@ path = "src/main.rs"
15 15 libluna-core.workspace = true
16 16 libluna-render.workspace = true
17 17 libluna-i18n.workspace = true
18 +libluna-platform.workspace = true
18 19
19 20 wayland-server = "0.31"
20 21 wayland-protocols = { version = "0.32", features = ["server"] }
21 -rustix = { version = "0.38", features = ["event", "fs", "time"] }
22 +rustix = { version = "0.38", features = ["event", "fs", "time", "mm"] }
@@ -2,21 +2,27 @@
2 2
3 3 use std::sync::Mutex;
4 4
5 +use libluna_render::ImageData;
5 6 use wayland_protocols::xdg::shell::server::{xdg_surface, xdg_toplevel};
6 7 use wayland_server::{
7 - protocol::{wl_callback, wl_compositor, wl_region, wl_surface},
8 + protocol::{wl_buffer, wl_callback, wl_compositor, wl_region, wl_surface},
8 9 Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
9 10 };
10 11
11 12 use crate::shell::XdgSurfaceData;
12 -use crate::state::State;
13 +use crate::shm::BufferData;
14 +use crate::state::{State, Window};
13 15
14 16 /// Dane powierzchni (`wl_surface`).
15 17 pub struct SurfaceData {
16 18 /// Oczekujące `frame`-callbacki do wywołania przy następnym `commit`.
17 19 pub frames: Mutex<Vec<wl_callback::WlCallback>>,
18 - /// Powiązana powierzchnia `xdg_surface` (jeśli klient ją utworzył).
20 + /// Powiązana powierzchnia `xdg_surface` (jeśli klient ją utworzyła).
19 21 pub xdg_surface: Mutex<Option<xdg_surface::XdgSurface>>,
22 + /// Ostatnio przypięty bufor (`wl_surface.attach`).
23 + pub buffer: Mutex<Option<wl_buffer::WlBuffer>>,
24 + /// Slot okna w modelu (przydzielany przy pierwszym commicie).
25 + pub slot: Mutex<Option<usize>>,
20 26 }
21 27
22 28 impl SurfaceData {
@@ -24,6 +30,8 @@ impl SurfaceData {
24 30 Self {
25 31 frames: Mutex::new(Vec::new()),
26 32 xdg_surface: Mutex::new(None),
33 + buffer: Mutex::new(None),
34 + slot: Mutex::new(None),
27 35 }
28 36 }
29 37 }
@@ -111,6 +119,9 @@ impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
111 119 let cb = data_init.init(callback, ());
112 120 data.frames.lock().unwrap().push(cb);
113 121 }
122 + wl_surface::Request::Attach { buffer, .. } => {
123 + *data.buffer.lock().unwrap() = buffer;
124 + }
114 125 wl_surface::Request::Commit => {
115 126 // Wywołaj oczekujące frame-callbacki.
116 127 let now = state.time_ms();
@@ -119,12 +130,66 @@ impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
119 130 }
120 131 // Pierwszy commit powierzchni z `xdg_surface` → wyślij konfigurację.
121 132 maybe_configure_xdg(state, data);
133 + // Skomponuj okno (jeśli to toplevel z buforem).
134 + composite(state, data);
122 135 }
123 136 _ => {}
124 137 }
125 138 }
126 139 }
127 140
141 +/// Skomponuj powierzchnię do modelu okien, jeśli jest to toplevel z buforem `wl_shm`.
142 +fn composite(state: &mut State, data: &SurfaceData) {
143 + let Some(xdg_surface) = data.xdg_surface.lock().unwrap().clone() else {
144 + return;
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 {
151 + return;
152 + }
153 + let Some(buffer) = data.buffer.lock().unwrap().clone() else {
154 + return;
155 + };
156 + let Some(buf_data) = buffer.data::<BufferData>() else {
157 + return;
158 + };
159 + let (w, h) = (buf_data.width, buf_data.height);
160 + let Some(rgba) = buf_data.to_rgba() else {
161 + return;
162 + };
163 + let image = std::sync::Arc::new(ImageData::new(w as u32, h as u32, rgba));
164 +
165 + let mut slot = data.slot.lock().unwrap();
166 + let mut windows = state.windows.lock().unwrap();
167 + let idx = match *slot {
168 + Some(i) => i,
169 + None => {
170 + let i = windows.len();
171 + windows.push(Window {
172 + x: 60.0 + i as f64 * 36.0,
173 + y: 60.0 + i as f64 * 36.0,
174 + width: w,
175 + height: h,
176 + data: image.clone(),
177 + });
178 + *slot = Some(i);
179 + i
180 + }
181 + };
182 + if let Some(window) = windows.get_mut(idx) {
183 + window.width = w;
184 + window.height = h;
185 + window.data = image;
186 + }
187 + drop(windows);
188 + drop(slot);
189 + // Pozwól klientowi ponownie użyć bufora.
190 + let _ = buffer.send_event(wl_buffer::Event::Release);
191 +}
192 +
128 193 /// Jeśli powierzchnia ma `xdg_surface` jeszcze nie skonfigurowany, wyślij configure.
129 194 fn maybe_configure_xdg(state: &mut State, data: &SurfaceData) {
130 195 let Some(xdg_surface) = data.xdg_surface.lock().unwrap().clone() else {
@@ -5,6 +5,7 @@
5 5 //! i deskryptorze backendu. Renderowanie docelowo przez `libluna-render`.
6 6
7 7 mod compositor;
8 +mod output;
8 9 mod seat;
9 10 mod shell;
10 11 mod shm;
@@ -47,6 +48,10 @@ fn main() -> Result<(), Box<dyn Error>> {
47 48 );
48 49
49 50 let mut state = State::default();
51 +
52 + // Wyjście nested: okno na nadrzędnym kompozytorze, budowane w osobnym wątku.
53 + output::spawn(std::sync::Arc::clone(&state.windows), 1280, 800);
54 +
50 55 let listener_fd = socket.as_raw_fd();
51 56
52 57 loop {
53 58 new file mode 100644
@@ -0,0 +1,50 @@
1 +//! Wyjście nested: Selene otwiera okno na nadrzędnym kompozytorze i rysuje scenę okien.
2 +//!
3 +//! Wykorzystujemy `libluna-platform` (strona klienta Wayland) jako backend wyświetlania —
4 +//! dzięki temu nie duplikujemy obsługi `wl_shm`/xdg-shell po stronie klienta. Wątek wyjścia
5 +//! odświeża scenę w pętli (przez `Frame::request_frame`) i komponuje okna z modelu.
6 +
7 +use libluna_core::{Color, Point, Rect, ThemeSettings};
8 +use libluna_platform::{run, AppConfig, Frame};
9 +use libluna_render::TextRun;
10 +
11 +use crate::state::WindowModel;
12 +
13 +/// Uruchamia wątek wyjścia (nested Wayland) o zadanym rozmiarze.
14 +pub fn spawn(model: WindowModel, width: u32, height: u32) {
15 + std::thread::spawn(move || {
16 + let config = AppConfig::new("Selene")
17 + .app_id("eu.paganlinux.Luna.Selene")
18 + .initial_size(width, height)
19 + .settings(ThemeSettings::default());
20 + let _ = run(config, move |frame: &mut Frame| {
21 + draw(frame, &model);
22 + // Utrzymuj klatki — model okien zmienia się poza tą pętlą.
23 + frame.request_frame();
24 + });
25 + });
26 +}
27 +
28 +fn draw(frame: &mut Frame, model: &WindowModel) {
29 + let size = frame.size;
30 + frame.scene.clear();
31 + frame.scene.fill_rect(
32 + Rect::from_size(size),
33 + Color::from_rgba8(0x1e, 0x20, 0x28, 0xff),
34 + );
35 +
36 + {
37 + let windows = model.lock().unwrap();
38 + for w in windows.iter() {
39 + frame.scene.image(
40 + Rect::from_xywh(w.x, w.y, w.width as f64, w.height as f64),
41 + w.data.clone(),
42 + );
43 + }
44 + }
45 +
46 + frame.scene.text(
47 + TextRun::new(Point::new(16.0, 28.0), "Selene — kompozytor Luna", 14.0)
48 + .with_color(Color::from_rgba8(0xdd, 0xdd, 0xdd, 0xff)),
49 + );
50 +}
@@ -1,4 +1,8 @@
1 -//! `wl_shm` — współdzielona pamięć klienta (bufory ARGB8888).
1 +//! `wl_shm` — współdzielona pamięć klienta (bufory ARGB8888 → RGBA).
2 +
3 +use std::ffi::c_void;
4 +use std::os::fd::OwnedFd;
5 +use std::sync::Arc;
2 6
3 7 use wayland_server::{
4 8 protocol::{wl_buffer, wl_shm, wl_shm_pool},
@@ -7,22 +11,75 @@ use wayland_server::{
7 11
8 12 use crate::state::State;
9 13
10 -/// Dane puli pamięci (`wl_shm_pool`).
11 -#[allow(dead_code)] // pola wykorzystamy przy komponowaniu buforów
14 +/// Dane puli pamięci (`wl_shm_pool`): deskryptor + rozmiar. Współdzielone przez bufory.
12 15 pub struct ShmPoolData {
16 + pub fd: OwnedFd,
13 17 pub size: usize,
14 18 }
15 19
16 -/// Dane bufora (`wl_buffer`).
17 -#[allow(dead_code)] // metadane potrzebne do komponowania
20 +/// Dane bufora (`wl_buffer`): odniesienie do puli + geometria.
18 21 pub struct BufferData {
22 + pub pool: Arc<ShmPoolData>,
23 + pub offset: usize,
19 24 pub width: i32,
20 25 pub height: i32,
21 26 pub stride: i32,
22 - pub offset: i32,
23 27 pub format: wl_shm::Format,
24 28 }
25 29
30 +impl BufferData {
31 + /// 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 + pub fn to_rgba(&self) -> Option<Vec<u8>> {
35 + if self.width <= 0 || self.height <= 0 || self.stride <= 0 {
36 + return None;
37 + }
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 + let (w, h) = (self.width as usize, self.height as usize);
54 + let stride = self.stride as usize;
55 + let mut out = vec![0u8; w * h * 4];
56 + for y in 0..h {
57 + let row = self.offset + y * stride;
58 + for x in 0..w {
59 + let i = row + x * 4;
60 + if i + 4 > bytes.len() {
61 + break;
62 + }
63 + let o = (y * w + x) * 4;
64 + // ARGB8888 (pamięć: B,G,R,A) → RGBA8; XRGB8888 → alfa wymuszona na 1.
65 + out[o] = bytes[i + 2];
66 + out[o + 1] = bytes[i + 1];
67 + out[o + 2] = bytes[i];
68 + out[o + 3] = if self.format == wl_shm::Format::Xrgb8888 {
69 + 255
70 + } else {
71 + bytes[i + 3]
72 + };
73 + }
74 + }
75 + // SAFETY: odmapowujemy dokładnie ten sam zakres.
76 + unsafe {
77 + let _ = rustix::mm::munmap(map, len);
78 + }
79 + Some(out)
80 + }
81 +}
82 +
26 83 impl GlobalDispatch<wl_shm::WlShm, ()> for State {
27 84 fn bind(
28 85 _state: &mut Self,
@@ -50,24 +107,25 @@ impl Dispatch<wl_shm::WlShm, ()> for State {
50 107 _handle: &DisplayHandle,
51 108 data_init: &mut DataInit<'_, Self>,
52 109 ) {
53 - if let wl_shm::Request::CreatePool { id, fd: _, size } = request {
110 + if let wl_shm::Request::CreatePool { id, fd, size } = request {
54 111 data_init.init(
55 112 id,
56 - ShmPoolData {
113 + Arc::new(ShmPoolData {
114 + fd,
57 115 size: size.max(0) as usize,
58 - },
116 + }),
59 117 );
60 118 }
61 119 }
62 120 }
63 121
64 -impl Dispatch<wl_shm_pool::WlShmPool, ShmPoolData> for State {
122 +impl Dispatch<wl_shm_pool::WlShmPool, Arc<ShmPoolData>> for State {
65 123 fn request(
66 124 _state: &mut Self,
67 125 _client: &Client,
68 126 _resource: &wl_shm_pool::WlShmPool,
69 127 request: wl_shm_pool::Request,
70 - _data: &ShmPoolData,
128 + data: &Arc<ShmPoolData>,
71 129 _handle: &DisplayHandle,
72 130 data_init: &mut DataInit<'_, Self>,
73 131 ) {
@@ -83,10 +141,11 @@ impl Dispatch<wl_shm_pool::WlShmPool, ShmPoolData> for State {
83 141 data_init.init(
84 142 id,
85 143 BufferData {
144 + pool: Arc::clone(data),
145 + offset: offset.max(0) as usize,
86 146 width,
87 147 height,
88 148 stride,
89 - offset,
90 149 format: format.into_result().unwrap_or(wl_shm::Format::Argb8888),
91 150 },
92 151 );
@@ -1,7 +1,23 @@
1 1 //! Globalny stan kompozytora Selene.
2 2
3 +use std::sync::{Arc, Mutex};
3 4 use std::time::Instant;
4 5
6 +use libluna_render::ImageData;
7 +
8 +/// Okno (toplevel) w modelu kompozycji: pozycja, rozmiar i gotowe piksele.
9 +pub struct Window {
10 + pub x: f64,
11 + pub y: f64,
12 + pub width: i32,
13 + pub height: i32,
14 + /// Piksele (premultiplied RGBA8) ostatnio zacommitowanego bufora.
15 + pub data: Arc<ImageData>,
16 +}
17 +
18 +/// Współdzielony model okien (pisany przez wątek protokołu, czytany przez wyjście).
19 +pub type WindowModel = Arc<Mutex<Vec<Window>>>;
20 +
5 21 /// Stan współdzielony przez wszystkie handlery protokołu.
6 22 pub struct State {
7 23 /// Start pracy kompozytora — do znaczników czasu (frame callbacks).
@@ -12,6 +28,8 @@ pub struct State {
12 28 pub clients: u32,
13 29 pub surfaces: u32,
14 30 pub toplevels: u32,
31 + /// Model okien współdzielony z wątkiem wyjścia.
32 + pub windows: WindowModel,
15 33 }
16 34
17 35 impl Default for State {
@@ -22,6 +40,7 @@ impl Default for State {
22 40 clients: 0,
23 41 surfaces: 0,
24 42 toplevels: 0,
43 + windows: Arc::new(Mutex::new(Vec::new())),
25 44 }
26 45 }
27 46 }
@@ -131,12 +131,14 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
131 131 ## M8 — Kompozytor Selene + środowisko Luna (`de/`)
132 132 - **Selene** (`de/selene`) — kompozytor Wayland w czystym Rust, **bez smithay**: własna warstwa
133 133 na `wayland-server` (rdzeń + xdg-shell) i bezpośrednia pętla `poll` po gnieździe i backendzie.
134 - ✅ Start (M-K0): globale `wl_compositor`, `wl_shm`, `xdg_wm_base`, `wl_seat`; cykl życia
135 - powierzchni + frame-callbacki, `xdg_surface.configure`, i18n (`libluna-i18n`), profil release
136 - z LTO. Uruchomienie: `cargo run -p selene` (szczegóły: `de/README.md`).
137 -- Dalej: backend wyświetlania (nested Wayland → potem DRM/KMS), komponowanie przez
138 - `libluna-render`, wejście (`libinput`/`evdev` + `wl_pointer`/`wl_keyboard`), `wl_output`,
139 - `xdg_popup`, `layer-shell`, schowek (`wl_data_device`), zarządzanie oknami/fokusem.
134 + ✅ M-K0: globale `wl_compositor`, `wl_shm`, `xdg_wm_base`, `wl_seat`; cykl życia powierzchni
135 + + frame-callbacki, `xdg_surface.configure`, i18n, profil release z LTO.
136 + ✅ M-K1: **wyjście nested** (`libluna-platform`) + kompozycja okien klientów (`wl_shm` →
137 + piksele → `libluna-render`); nowy prymityw `Scene::image`/`ImageData` w rendererze.
138 + Uruchomienie: `cargo run -p selene` (szczegóły: `de/README.md`).
139 +- Dalej: **wejście** (`libinput`/`evdev` + `wl_pointer`/`wl_keyboard`), zarządzanie oknami/fokusem,
140 + `wl_output`, `xdg_popup`, `layer-shell`, schowek (`wl_data_device`), backend DRM/KMS + GLES,
141 + oraz optymalizacje (cache mapy shm, render przy zmianie modelu).
140 142 - **Luna** (`de/luna`) — powłoka środowiska (panel, launcher, OSD) na `libluna-widgets`.
141 143
142 144 ## Później (poza MVP)