← luna

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