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)