Commit 416e032
13
files
+756
added
-1
deleted
@@ -1,3 +1,4 @@
1
1
/target
2
2
**/*.rs.bk
3
3
*.pdb
4
+.selene-runtime/
@@ -444,6 +444,18 @@ dependencies = [
444
444
"windows-sys 0.61.2",
445
445
]
446
446
447
+[[package]]
448
+name = "selene"
449
+version = "0.0.1"
450
+dependencies = [
451
+ "libluna-core",
452
+ "libluna-i18n",
453
+ "libluna-render",
454
+ "rustix 0.38.44",
455
+ "wayland-protocols",
456
+ "wayland-server",
457
+]
458
+
447
459
[[package]]
448
460
name = "shlex"
449
461
version = "2.0.1"
@@ -642,6 +654,7 @@ dependencies = [
642
654
"wayland-backend",
643
655
"wayland-client",
644
656
"wayland-scanner",
657
+ "wayland-server",
645
658
]
646
659
647
660
[[package]]
@@ -668,6 +681,19 @@ dependencies = [
668
681
"quote",
669
682
]
670
683
684
+[[package]]
685
+name = "wayland-server"
686
+version = "0.31.14"
687
+source = "registry+https://github.com/rust-lang/crates.io-index"
688
+checksum = "0dde9c29be0f723a573977de51ee455bf3dfa03652730a74f9dd3b337e374d75"
689
+dependencies = [
690
+ "bitflags 2.13.2",
691
+ "downcast-rs",
692
+ "rustix 1.1.5",
693
+ "wayland-backend",
694
+ "wayland-scanner",
695
+]
696
+
671
697
[[package]]
672
698
name = "wayland-sys"
673
699
version = "0.31.11"
@@ -8,6 +8,7 @@ members = [
8
8
"crates/libluna-platform",
9
9
"crates/libluna-widgets",
10
10
"apps/luna-demo",
11
+ "de/selene",
11
12
]
12
13
13
14
[workspace.package]
@@ -27,3 +28,12 @@ libluna-widgets = { path = "crates/libluna-widgets", version = "0.0.1" }
27
28
libluna = { path = "crates/libluna", version = "0.0.1" }
28
29
29
30
tiny-skia = "0.11"
31
+
32
+# Optymalizacja wydania: pełny LTO i jedna jednostka kodegenu (mniejszy, szybszy binarny
33
+# kompozytor + środowisko). `panic = "unwind"` świadomie zostawione — panika klienta nie
34
+# powinna zabijać całej sesji.
35
+[profile.release]
36
+opt-level = 3
37
+lto = "thin"
38
+codegen-units = 1
39
+strip = true
@@ -92,6 +92,9 @@ libluna/
92
92
libluna-widgets/ # widgety: ViewSwitcher, NavList, Menu, Button, Switch, Slider, DropDown, HeaderBar, TabBar/TabView, BottomSheet, Pager, PathBar, SearchEntry, ItemView, ScrollView, Stepper, CheckButton, RadioGroup, Card, ActionRow
93
93
apps/
94
94
luna-demo/ # demo aplikacji środowiska Luna; też render offline do PNG
95
+ de/ # środowisko graficzne Luna
96
+ README.md # opis Selene (kompozytor) + nazewnictwo
97
+ selene/ # kompozytor Wayland (bez smithay: wayland-server + własna pętla)
95
98
templates/
96
99
minimal-app/ # szablon startowy aplikacji
97
100
tools/
@@ -448,4 +451,4 @@ i ujednolicić warstwę UI w czystym Rust. Bazą jest tu **libLuna**; dotychczas
448
451
449
452
## Licencja
450
453
451
-MPL-2.0 (do potwierdzenia przez właściciela projektu).
454
+MIT
452
455
new file mode 100644
@@ -0,0 +1,64 @@
1
+# Selene — kompozytor Wayland środowiska Luna
2
+
3
+**English** — Selene is the Wayland compositor at the heart of the **Luna** desktop. It is
4
+written in **pure Rust** and deliberately **does not depend on smithay**: the protocol is
5
+handled by our own thin layer on top of `wayland-server` (core + xdg-shell), and the event
6
+loop is a direct `poll` over the listening socket and the backend descriptor. `libLuna` is a
7
+normal crate dependency (`libluna-core`, `libluna-render`, `libluna-i18n`).
8
+
9
+**Polski** — Selene to kompozytor Wayland będący sercem środowiska **Luna**. Napisany w
10
+**czystym Rust** i świadomie **bez zależności od smithay**: protokół obsługuje własna cienka
11
+warstwa na `wayland-server` (rdzeń + xdg-shell), a pętla zdarzeń to bezpośredni `poll` po
12
+gnieździe nasłuchu i deskryptorze backendu. `libLuna` jest zwykłą zależnością crate'ową.
13
+
14
+## Nazewnictwo / Naming
15
+
16
+| Warstwa / Layer | Nazwa / Name | Opis |
17
+| --- | --- | --- |
18
+| Toolkit GUI | **libLuna** | widgety, render, i18n (`crates/libluna-*`) |
19
+| Środowisko | **Luna** | pulpit (panel, launcher, ustawienia) — `de/luna` (wkrótce) |
20
+| Kompozytor | **Selene** | serwer Wayland — `de/selene` |
21
+
22
+## Co już działa / What works (M-K0)
23
+
24
+- Globalne interfejsy: `wl_compositor` (v4), `wl_shm`, `xdg_wm_base` (v1), `wl_seat` (v1).
25
+- Cykl życia powierzchni: `wl_surface` (`attach`/`damage`/`frame`/`commit`), `wl_callback`
26
+ (wywołanie `done` przy commicie), `wl_region`.
27
+- `wl_shm`: `create_pool` → `wl_shm_pool.create_buffer` → `wl_buffer` (metadane bufora).
28
+- xdg-shell: `get_xdg_surface` → `get_toplevel`, pierwszy `commit` wysyła `configure`
29
+ (oraz `xdg_toplevel.configure`), `ack_configure`, `set_title`/`set_app_id`.
30
+- Minimalny `wl_seat` (obiekty wejścia tworzone, bez zdarzeń).
31
+- Wielojęzyczność: lokalizacja z otoczenia + `i18n/` ładowane w runtime (`libluna-i18n`).
32
+- Optymalizacja: `[profile.release]` z pełnym LTO i 1 jednostką kodegenu.
33
+
34
+## Jeszcze nie / Not yet
35
+
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`).
41
+
42
+## Uruchomienie / Running
43
+
44
+```sh
45
+# katalog runtime z prawem 0700
46
+mkdir -p .selene-runtime && chmod 700 .selene-runtime
47
+XDG_RUNTIME_DIR="$PWD/.selene-runtime" cargo run -p selene
48
+# → "Selene compositor started (wayland-0)" + gniazdo wayland-0 w katalogu runtime
49
+```
50
+
51
+Klienta podłączysz ustawiając `WAYLAND_DISPLAY=wayland-0` i `XDG_RUNTIME_DIR` na ten sam
52
+katalog (renderowanie pojawi się, gdy wejdzie backend wyświetlania).
53
+
54
+## Architektura / Architecture
55
+
56
+```
57
+de/selene/src/
58
+ 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
62
+ shell.rs # xdg_wm_base / xdg_surface / xdg_toplevel / xdg_positioner
63
+ seat.rs # wl_seat (minimalnie)
64
+```
0
65
new file mode 100644
@@ -0,0 +1,21 @@
1
+[package]
2
+name = "selene"
3
+version.workspace = true
4
+edition.workspace = true
5
+license.workspace = true
6
+rust-version.workspace = true
7
+authors.workspace = true
8
+description = "Selene — kompozytor Wayland środowiska Luna (bez smithay; własna implementacja na wayland-server)."
9
+
10
+[[bin]]
11
+name = "selene"
12
+path = "src/main.rs"
13
+
14
+[dependencies]
15
+libluna-core.workspace = true
16
+libluna-render.workspace = true
17
+libluna-i18n.workspace = true
18
+
19
+wayland-server = "0.31"
20
+wayland-protocols = { version = "0.32", features = ["server"] }
21
+rustix = { version = "0.38", features = ["event", "fs", "time"] }
0
22
new file mode 100644
@@ -0,0 +1,152 @@
1
+//! `wl_compositor`, `wl_surface`, `wl_region`, `wl_callback` — rdzeń powierzchni.
2
+
3
+use std::sync::Mutex;
4
+
5
+use wayland_protocols::xdg::shell::server::{xdg_surface, xdg_toplevel};
6
+use wayland_server::{
7
+ protocol::{wl_callback, wl_compositor, wl_region, wl_surface},
8
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
9
+};
10
+
11
+use crate::shell::XdgSurfaceData;
12
+use crate::state::State;
13
+
14
+/// Dane powierzchni (`wl_surface`).
15
+pub struct SurfaceData {
16
+ /// Oczekujące `frame`-callbacki do wywołania przy następnym `commit`.
17
+ pub frames: Mutex<Vec<wl_callback::WlCallback>>,
18
+ /// Powiązana powierzchnia `xdg_surface` (jeśli klient ją utworzył).
19
+ pub xdg_surface: Mutex<Option<xdg_surface::XdgSurface>>,
20
+}
21
+
22
+impl SurfaceData {
23
+ pub fn new() -> Self {
24
+ Self {
25
+ frames: Mutex::new(Vec::new()),
26
+ xdg_surface: Mutex::new(None),
27
+ }
28
+ }
29
+}
30
+
31
+impl Default for SurfaceData {
32
+ fn default() -> Self {
33
+ Self::new()
34
+ }
35
+}
36
+
37
+impl GlobalDispatch<wl_compositor::WlCompositor, ()> for State {
38
+ fn bind(
39
+ _state: &mut Self,
40
+ _handle: &DisplayHandle,
41
+ _client: &Client,
42
+ resource: New<wl_compositor::WlCompositor>,
43
+ _data: &(),
44
+ data_init: &mut DataInit<'_, Self>,
45
+ ) {
46
+ data_init.init(resource, ());
47
+ }
48
+}
49
+
50
+impl Dispatch<wl_compositor::WlCompositor, ()> for State {
51
+ fn request(
52
+ state: &mut Self,
53
+ _client: &Client,
54
+ _resource: &wl_compositor::WlCompositor,
55
+ request: wl_compositor::Request,
56
+ _data: &(),
57
+ _handle: &DisplayHandle,
58
+ data_init: &mut DataInit<'_, Self>,
59
+ ) {
60
+ match request {
61
+ wl_compositor::Request::CreateSurface { id } => {
62
+ data_init.init(id, SurfaceData::new());
63
+ state.surfaces += 1;
64
+ }
65
+ wl_compositor::Request::CreateRegion { id } => {
66
+ data_init.init(id, ());
67
+ }
68
+ _ => {}
69
+ }
70
+ }
71
+}
72
+
73
+impl Dispatch<wl_region::WlRegion, ()> for State {
74
+ fn request(
75
+ _state: &mut Self,
76
+ _client: &Client,
77
+ _resource: &wl_region::WlRegion,
78
+ _request: wl_region::Request,
79
+ _data: &(),
80
+ _handle: &DisplayHandle,
81
+ _data_init: &mut DataInit<'_, Self>,
82
+ ) {
83
+ }
84
+}
85
+
86
+impl Dispatch<wl_callback::WlCallback, ()> for State {
87
+ fn request(
88
+ _state: &mut Self,
89
+ _client: &Client,
90
+ _resource: &wl_callback::WlCallback,
91
+ _request: wl_callback::Request,
92
+ _data: &(),
93
+ _handle: &DisplayHandle,
94
+ _data_init: &mut DataInit<'_, Self>,
95
+ ) {
96
+ }
97
+}
98
+
99
+impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
100
+ fn request(
101
+ state: &mut Self,
102
+ _client: &Client,
103
+ _resource: &wl_surface::WlSurface,
104
+ request: wl_surface::Request,
105
+ data: &SurfaceData,
106
+ _handle: &DisplayHandle,
107
+ data_init: &mut DataInit<'_, Self>,
108
+ ) {
109
+ match request {
110
+ wl_surface::Request::Frame { callback } => {
111
+ let cb = data_init.init(callback, ());
112
+ data.frames.lock().unwrap().push(cb);
113
+ }
114
+ wl_surface::Request::Commit => {
115
+ // Wywołaj oczekujące frame-callbacki.
116
+ let now = state.time_ms();
117
+ for cb in data.frames.lock().unwrap().drain(..) {
118
+ let _ = cb.send_event(wl_callback::Event::Done { callback_data: now });
119
+ }
120
+ // Pierwszy commit powierzchni z `xdg_surface` → wyślij konfigurację.
121
+ maybe_configure_xdg(state, data);
122
+ }
123
+ _ => {}
124
+ }
125
+ }
126
+}
127
+
128
+/// Jeśli powierzchnia ma `xdg_surface` jeszcze nie skonfigurowany, wyślij configure.
129
+fn maybe_configure_xdg(state: &mut State, data: &SurfaceData) {
130
+ let Some(xdg_surface) = data.xdg_surface.lock().unwrap().clone() else {
131
+ return;
132
+ };
133
+ let Some(xdg_data) = xdg_surface.data::<XdgSurfaceData>() else {
134
+ return;
135
+ };
136
+ if *xdg_data.configured.lock().unwrap() {
137
+ return;
138
+ }
139
+ let serial = state.next_serial();
140
+ let _ = xdg_surface.send_event(xdg_surface::Event::Configure { serial });
141
+ if let Some(toplevel) = xdg_data.toplevel.lock().unwrap().as_ref() {
142
+ let _ = toplevel.send_event(xdg_toplevel::Event::Configure {
143
+ width: 1024,
144
+ height: 720,
145
+ states: vec![3], // ACTIVATED
146
+ });
147
+ let _ = toplevel.send_event(xdg_toplevel::Event::WmCapabilities {
148
+ capabilities: vec![],
149
+ });
150
+ }
151
+ *xdg_data.configured.lock().unwrap() = true;
152
+}
0
153
new file mode 100644
@@ -0,0 +1,73 @@
1
+//! Selene — kompozytor Wayland środowiska Luna.
2
+//!
3
+//! Świadomie **bez smithay**: protokół obsługujemy własną warstwą na `wayland-server`
4
+//! (rdzeń + xdg-shell), a pętla zdarzeń to bezpośredni `poll` po gnieździe nasłuchu
5
+//! i deskryptorze backendu. Renderowanie docelowo przez `libluna-render`.
6
+
7
+mod compositor;
8
+mod seat;
9
+mod shell;
10
+mod shm;
11
+mod state;
12
+
13
+use std::error::Error;
14
+use std::os::fd::{AsRawFd, BorrowedFd};
15
+use std::sync::Arc;
16
+
17
+use wayland_protocols::xdg::shell::server::xdg_wm_base;
18
+use wayland_server::protocol::{wl_compositor, wl_seat, wl_shm};
19
+use wayland_server::{Display, ListeningSocket};
20
+
21
+use state::State;
22
+
23
+fn main() -> Result<(), Box<dyn Error>> {
24
+ // Wielojęzyczność: lokalizacja z otoczenia + katalogi `i18n/` doładowane w runtime.
25
+ libluna_i18n::set_locale(libluna_i18n::Locale::from_env());
26
+ libluna_i18n::load_catalogs_from_dirs();
27
+
28
+ let mut display: Display<State> = Display::new()?;
29
+ let mut handle = display.handle();
30
+
31
+ // Globalne interfejsy protokołu.
32
+ handle.create_global::<State, wl_compositor::WlCompositor, ()>(4, ());
33
+ handle.create_global::<State, wl_shm::WlShm, ()>(1, ());
34
+ handle.create_global::<State, xdg_wm_base::XdgWmBase, ()>(1, ());
35
+ handle.create_global::<State, wl_seat::WlSeat, ()>(1, ());
36
+
37
+ // Gniazdo nasłuchu: `wayland-0`, `wayland-1`, …
38
+ let socket =
39
+ ListeningSocket::bind_auto("wayland", 0..33).map_err(|e| format!("bind socket: {e:?}"))?;
40
+ println!(
41
+ "{} ({})",
42
+ libluna_i18n::tr("Selene compositor started"),
43
+ socket
44
+ .socket_name()
45
+ .map(|s| s.to_string_lossy().into_owned())
46
+ .unwrap_or_default()
47
+ );
48
+
49
+ let mut state = State::default();
50
+ let listener_fd = socket.as_raw_fd();
51
+
52
+ loop {
53
+ let backend_fd = display.backend().poll_fd().as_raw_fd();
54
+
55
+ let listener = unsafe { BorrowedFd::borrow_raw(listener_fd) };
56
+ let backend = unsafe { BorrowedFd::borrow_raw(backend_fd) };
57
+ let mut fds = [
58
+ rustix::event::PollFd::new(&listener, rustix::event::PollFlags::IN),
59
+ rustix::event::PollFd::new(&backend, rustix::event::PollFlags::IN),
60
+ ];
61
+ rustix::event::poll(&mut fds, -1)?;
62
+
63
+ if fds[0].revents().contains(rustix::event::PollFlags::IN) {
64
+ while let Ok(Some(stream)) = socket.accept() {
65
+ handle.insert_client(stream, Arc::new(()))?;
66
+ state.clients += 1;
67
+ }
68
+ }
69
+
70
+ display.dispatch_clients(&mut state)?;
71
+ display.flush_clients()?;
72
+ }
73
+}
0
74
new file mode 100644
@@ -0,0 +1,70 @@
1
+//! `wl_seat` — minimalne wejście (na razie bez zdarzeń; obiekty tworzone, by klienci działali).
2
+
3
+use wayland_server::{
4
+ protocol::{wl_keyboard, wl_pointer, wl_seat, wl_touch},
5
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
6
+};
7
+
8
+use crate::state::State;
9
+
10
+impl GlobalDispatch<wl_seat::WlSeat, ()> for State {
11
+ fn bind(
12
+ _state: &mut Self,
13
+ _handle: &DisplayHandle,
14
+ _client: &Client,
15
+ resource: New<wl_seat::WlSeat>,
16
+ _data: &(),
17
+ data_init: &mut DataInit<'_, Self>,
18
+ ) {
19
+ let seat = data_init.init(resource, ());
20
+ let _ = seat.send_event(wl_seat::Event::Capabilities {
21
+ capabilities: wayland_server::WEnum::Value(wl_seat::Capability::empty()),
22
+ });
23
+ }
24
+}
25
+
26
+impl Dispatch<wl_seat::WlSeat, ()> for State {
27
+ fn request(
28
+ _state: &mut Self,
29
+ _client: &Client,
30
+ _resource: &wl_seat::WlSeat,
31
+ request: wl_seat::Request,
32
+ _data: &(),
33
+ _handle: &DisplayHandle,
34
+ data_init: &mut DataInit<'_, Self>,
35
+ ) {
36
+ match request {
37
+ wl_seat::Request::GetPointer { id } => {
38
+ data_init.init(id, ());
39
+ }
40
+ wl_seat::Request::GetKeyboard { id } => {
41
+ data_init.init(id, ());
42
+ }
43
+ wl_seat::Request::GetTouch { id } => {
44
+ data_init.init(id, ());
45
+ }
46
+ _ => {}
47
+ }
48
+ }
49
+}
50
+
51
+macro_rules! empty_dispatch {
52
+ ($iface:ty) => {
53
+ impl Dispatch<$iface, ()> for State {
54
+ fn request(
55
+ _state: &mut Self,
56
+ _client: &Client,
57
+ _resource: &$iface,
58
+ _request: <$iface as wayland_server::Resource>::Request,
59
+ _data: &(),
60
+ _handle: &DisplayHandle,
61
+ _data_init: &mut DataInit<'_, Self>,
62
+ ) {
63
+ }
64
+ }
65
+ };
66
+}
67
+
68
+empty_dispatch!(wl_pointer::WlPointer);
69
+empty_dispatch!(wl_keyboard::WlKeyboard);
70
+empty_dispatch!(wl_touch::WlTouch);
0
71
new file mode 100644
@@ -0,0 +1,154 @@
1
+//! `xdg_wm_base` / `xdg_surface` / `xdg_toplevel` — okna aplikacji (protokół xdg-shell).
2
+
3
+use std::sync::Mutex;
4
+
5
+use wayland_protocols::xdg::shell::server::{
6
+ xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
7
+};
8
+use wayland_server::{
9
+ protocol::wl_surface::WlSurface, Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch,
10
+ New, Resource,
11
+};
12
+
13
+use crate::compositor::SurfaceData;
14
+use crate::state::State;
15
+
16
+/// Dane `xdg_surface`.
17
+#[allow(dead_code)] // `wl_surface` przyda się przy renderowaniu/geometrii w kolejnych krokach
18
+pub struct XdgSurfaceData {
19
+ /// Powiązana powierzchnia `wl_surface`.
20
+ pub wl_surface: WlSurface,
21
+ /// Utworzona powierzchnia najwyższego poziomu (jeśli `get_toplevel`).
22
+ pub toplevel: Mutex<Option<xdg_toplevel::XdgToplevel>>,
23
+ /// Czy wysłano już pierwszą konfigurację.
24
+ pub configured: Mutex<bool>,
25
+}
26
+
27
+impl XdgSurfaceData {
28
+ pub fn new(wl_surface: WlSurface) -> Self {
29
+ Self {
30
+ wl_surface,
31
+ toplevel: Mutex::new(None),
32
+ configured: Mutex::new(false),
33
+ }
34
+ }
35
+}
36
+
37
+/// Dane `xdg_toplevel`.
38
+pub struct XdgToplevelData {
39
+ pub title: Mutex<String>,
40
+ pub app_id: Mutex<String>,
41
+}
42
+
43
+impl XdgToplevelData {
44
+ pub fn new() -> Self {
45
+ Self {
46
+ title: Mutex::new(String::new()),
47
+ app_id: Mutex::new(String::new()),
48
+ }
49
+ }
50
+}
51
+
52
+impl Default for XdgToplevelData {
53
+ fn default() -> Self {
54
+ Self::new()
55
+ }
56
+}
57
+
58
+impl GlobalDispatch<xdg_wm_base::XdgWmBase, ()> for State {
59
+ fn bind(
60
+ _state: &mut Self,
61
+ _handle: &DisplayHandle,
62
+ _client: &Client,
63
+ resource: New<xdg_wm_base::XdgWmBase>,
64
+ _data: &(),
65
+ data_init: &mut DataInit<'_, Self>,
66
+ ) {
67
+ data_init.init(resource, ());
68
+ }
69
+}
70
+
71
+impl Dispatch<xdg_wm_base::XdgWmBase, ()> for State {
72
+ fn request(
73
+ _state: &mut Self,
74
+ _client: &Client,
75
+ _resource: &xdg_wm_base::XdgWmBase,
76
+ request: xdg_wm_base::Request,
77
+ _data: &(),
78
+ _handle: &DisplayHandle,
79
+ data_init: &mut DataInit<'_, Self>,
80
+ ) {
81
+ match request {
82
+ xdg_wm_base::Request::CreatePositioner { id } => {
83
+ data_init.init(id, ());
84
+ }
85
+ xdg_wm_base::Request::GetXdgSurface { id, surface } => {
86
+ let xdg = data_init.init(id, XdgSurfaceData::new(surface.clone()));
87
+ // Zapamiętaj powiązanie po stronie `wl_surface` (do konfiguracji przy commit).
88
+ if let Some(sd) = surface.data::<SurfaceData>() {
89
+ *sd.xdg_surface.lock().unwrap() = Some(xdg);
90
+ }
91
+ }
92
+ _ => {}
93
+ }
94
+ }
95
+}
96
+
97
+impl Dispatch<xdg_surface::XdgSurface, XdgSurfaceData> for State {
98
+ fn request(
99
+ _state: &mut Self,
100
+ _client: &Client,
101
+ _resource: &xdg_surface::XdgSurface,
102
+ request: xdg_surface::Request,
103
+ data: &XdgSurfaceData,
104
+ _handle: &DisplayHandle,
105
+ data_init: &mut DataInit<'_, Self>,
106
+ ) {
107
+ match request {
108
+ xdg_surface::Request::GetToplevel { id } => {
109
+ let toplevel = data_init.init(id, XdgToplevelData::new());
110
+ *data.toplevel.lock().unwrap() = Some(toplevel);
111
+ }
112
+ xdg_surface::Request::AckConfigure { .. } => {
113
+ // Konfiguracja potwierdzona — nic więcej nie trzeba po stronie MVP.
114
+ }
115
+ _ => {}
116
+ }
117
+ }
118
+}
119
+
120
+impl Dispatch<xdg_toplevel::XdgToplevel, XdgToplevelData> for State {
121
+ fn request(
122
+ state: &mut Self,
123
+ _client: &Client,
124
+ _resource: &xdg_toplevel::XdgToplevel,
125
+ request: xdg_toplevel::Request,
126
+ data: &XdgToplevelData,
127
+ _handle: &DisplayHandle,
128
+ _data_init: &mut DataInit<'_, Self>,
129
+ ) {
130
+ match request {
131
+ xdg_toplevel::Request::SetTitle { title } => {
132
+ *data.title.lock().unwrap() = title;
133
+ state.toplevels += 1;
134
+ }
135
+ xdg_toplevel::Request::SetAppId { app_id } => {
136
+ *data.app_id.lock().unwrap() = app_id;
137
+ }
138
+ _ => {}
139
+ }
140
+ }
141
+}
142
+
143
+impl Dispatch<xdg_positioner::XdgPositioner, ()> for State {
144
+ fn request(
145
+ _state: &mut Self,
146
+ _client: &Client,
147
+ _resource: &xdg_positioner::XdgPositioner,
148
+ _request: xdg_positioner::Request,
149
+ _data: &(),
150
+ _handle: &DisplayHandle,
151
+ _data_init: &mut DataInit<'_, Self>,
152
+ ) {
153
+ }
154
+}
0
155
new file mode 100644
@@ -0,0 +1,108 @@
1
+//! `wl_shm` — współdzielona pamięć klienta (bufory ARGB8888).
2
+
3
+use wayland_server::{
4
+ protocol::{wl_buffer, wl_shm, wl_shm_pool},
5
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
6
+};
7
+
8
+use crate::state::State;
9
+
10
+/// Dane puli pamięci (`wl_shm_pool`).
11
+#[allow(dead_code)] // pola wykorzystamy przy komponowaniu buforów
12
+pub struct ShmPoolData {
13
+ pub size: usize,
14
+}
15
+
16
+/// Dane bufora (`wl_buffer`).
17
+#[allow(dead_code)] // metadane potrzebne do komponowania
18
+pub struct BufferData {
19
+ pub width: i32,
20
+ pub height: i32,
21
+ pub stride: i32,
22
+ pub offset: i32,
23
+ pub format: wl_shm::Format,
24
+}
25
+
26
+impl GlobalDispatch<wl_shm::WlShm, ()> for State {
27
+ fn bind(
28
+ _state: &mut Self,
29
+ _handle: &DisplayHandle,
30
+ _client: &Client,
31
+ resource: New<wl_shm::WlShm>,
32
+ _data: &(),
33
+ data_init: &mut DataInit<'_, Self>,
34
+ ) {
35
+ let shm = data_init.init(resource, ());
36
+ // Zadeklaruj obsługiwany format (klienci muszą znać choć jeden).
37
+ let _ = shm.send_event(wl_shm::Event::Format {
38
+ format: wayland_server::WEnum::Value(wl_shm::Format::Argb8888),
39
+ });
40
+ }
41
+}
42
+
43
+impl Dispatch<wl_shm::WlShm, ()> for State {
44
+ fn request(
45
+ _state: &mut Self,
46
+ _client: &Client,
47
+ _resource: &wl_shm::WlShm,
48
+ request: wl_shm::Request,
49
+ _data: &(),
50
+ _handle: &DisplayHandle,
51
+ data_init: &mut DataInit<'_, Self>,
52
+ ) {
53
+ if let wl_shm::Request::CreatePool { id, fd: _, size } = request {
54
+ data_init.init(
55
+ id,
56
+ ShmPoolData {
57
+ size: size.max(0) as usize,
58
+ },
59
+ );
60
+ }
61
+ }
62
+}
63
+
64
+impl Dispatch<wl_shm_pool::WlShmPool, ShmPoolData> for State {
65
+ fn request(
66
+ _state: &mut Self,
67
+ _client: &Client,
68
+ _resource: &wl_shm_pool::WlShmPool,
69
+ request: wl_shm_pool::Request,
70
+ _data: &ShmPoolData,
71
+ _handle: &DisplayHandle,
72
+ data_init: &mut DataInit<'_, Self>,
73
+ ) {
74
+ if let wl_shm_pool::Request::CreateBuffer {
75
+ id,
76
+ offset,
77
+ width,
78
+ height,
79
+ stride,
80
+ format,
81
+ } = request
82
+ {
83
+ data_init.init(
84
+ id,
85
+ BufferData {
86
+ width,
87
+ height,
88
+ stride,
89
+ offset,
90
+ format: format.into_result().unwrap_or(wl_shm::Format::Argb8888),
91
+ },
92
+ );
93
+ }
94
+ }
95
+}
96
+
97
+impl Dispatch<wl_buffer::WlBuffer, BufferData> for State {
98
+ fn request(
99
+ _state: &mut Self,
100
+ _client: &Client,
101
+ _resource: &wl_buffer::WlBuffer,
102
+ _request: wl_buffer::Request,
103
+ _data: &BufferData,
104
+ _handle: &DisplayHandle,
105
+ _data_init: &mut DataInit<'_, Self>,
106
+ ) {
107
+ }
108
+}
0
109
new file mode 100644
@@ -0,0 +1,62 @@
1
+//! Globalny stan kompozytora Selene.
2
+
3
+use std::time::Instant;
4
+
5
+/// Stan współdzielony przez wszystkie handlery protokołu.
6
+pub struct State {
7
+ /// Start pracy kompozytora — do znaczników czasu (frame callbacks).
8
+ pub start: Instant,
9
+ /// Licznik seriali (dla `xdg_surface.configure` itp.).
10
+ serial: u32,
11
+ /// Liczniki diagnostyczne (log startowy / debug).
12
+ pub clients: u32,
13
+ pub surfaces: u32,
14
+ pub toplevels: u32,
15
+}
16
+
17
+impl Default for State {
18
+ fn default() -> Self {
19
+ Self {
20
+ start: Instant::now(),
21
+ serial: 0,
22
+ clients: 0,
23
+ surfaces: 0,
24
+ toplevels: 0,
25
+ }
26
+ }
27
+}
28
+
29
+impl State {
30
+ /// Kolejny serial protokołu.
31
+ pub fn next_serial(&mut self) -> u32 {
32
+ self.serial = self.serial.wrapping_add(1).max(1);
33
+ self.serial
34
+ }
35
+
36
+ /// Czas od startu w milisekundach (dla `wl_callback.done`).
37
+ pub fn time_ms(&self) -> u32 {
38
+ self.start.elapsed().as_millis() as u32
39
+ }
40
+}
41
+
42
+#[cfg(test)]
43
+mod tests {
44
+ use super::*;
45
+
46
+ #[test]
47
+ fn serials_are_non_zero_and_increasing() {
48
+ let mut s = State::default();
49
+ let a = s.next_serial();
50
+ let b = s.next_serial();
51
+ assert!(a >= 1);
52
+ assert!(b > a);
53
+ }
54
+
55
+ #[test]
56
+ fn time_ms_is_monotonic() {
57
+ let s = State::default();
58
+ let t0 = s.time_ms();
59
+ let t1 = s.time_ms();
60
+ assert!(t1 >= t0);
61
+ }
62
+}
@@ -128,6 +128,17 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
128
128
- Potem `karton-files` (`window.c` ~5100 linii) — etapami.
129
129
- Opcjonalnie ujednolicenie `karton-shell` na libLunę (dziś cairo/pango bezpośrednio).
130
130
131
+## M8 — Kompozytor Selene + środowisko Luna (`de/`)
132
+- **Selene** (`de/selene`) — kompozytor Wayland w czystym Rust, **bez smithay**: własna warstwa
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.
140
+- **Luna** (`de/luna`) — powłoka środowiska (panel, launcher, OSD) na `libluna-widgets`.
141
+
131
142
## Później (poza MVP)
132
143
- Dostępność AT-SPI, IME, schowek, drag & drop, animacje, backend GPU, mini-CSS dla motywów.
133
144