Commit 0122c49
9
files
+741
added
-50
deleted
@@ -19,9 +19,10 @@ 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-K1)
22
+## Co już działa / What works (M-K2)
23
23
24
-- Globalne interfejsy: `wl_compositor` (v4), `wl_shm`, `xdg_wm_base` (v1), `wl_seat` (v1).
24
+- Globalne interfejsy: `wl_compositor` (v4), `wl_shm`, **`wl_output`** (v4), `xdg_wm_base` (v1),
25
+ **`wl_seat`** (v1) z **wskaźnikiem i klawiaturą**.
25
26
- Cykl życia powierzchni: `wl_surface` (`attach`/`damage`/`frame`/`commit`), `wl_callback`
26
27
(wywołanie `done` przy commicie), `wl_region`.
27
28
- `wl_shm`: `create_pool` → `wl_shm_pool.create_buffer` → `wl_buffer`; piksele bufora są
@@ -29,21 +30,29 @@ gnieździe nasłuchu i deskryptorze backendu. `libLuna` jest zwykłą zależnoś
29
30
- xdg-shell: `get_xdg_surface` → `get_toplevel`, pierwszy `commit` wysyła `configure`
30
31
(oraz `xdg_toplevel.configure`), `ack_configure`, `set_title`/`set_app_id`.
31
32
- **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`.
34
-- Minimalny `wl_seat` (obiekty wejścia tworzone, bez zdarzeń).
35
-- Wielojęzyczność: lokalizacja z otoczenia + `i18n/` ładowane w runtime (`libluna-i18n`).
36
-- Optymalizacja: `[profile.release]` z pełnym LTO i 1 jednostką kodegenu.
33
+ wątek), a `libluna-render` komponuje scenę (tło + okna w kolejności `z`). Renderer ma
34
+ prymityw `Scene::image`/`ImageData`.
35
+- **Wejście**: magistrala (kolejka + para gniazd do wybudzania `poll`). Wątek wyjścia
36
+ przekazuje zdarzenia od rodzica; protokół trasuje je do klientów:
37
+ `wl_pointer` (`enter`/`motion`/`button`/`axis`/`leave`) i `wl_keyboard` (`keymap`
38
+ z wbudowanej mapy XKB, `enter`/`leave`/`key`/`modifiers` z syntezą modyfikatorów).
39
+- **Zarządzanie oknami (pływające)**: trafienie w okno = fokus + podniesienie (`z`),
40
+ przeciągnięcie lewym przyciskiem przenosi okno; kaskada przy starcie.
41
+- **Monitory**: model `Vec<OutputInfo>` (gotowy na wiele); okno nested = jeden monitor
42
+ (rozmiar śledzony z konfiguracji okna).
43
+- Wielojęzyczność (`libluna-i18n`) i optymalizacja (`[profile.release]` LTO).
37
44
38
45
## Jeszcze nie / Not yet
39
46
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).
47
+- **Wiele monitorów**: realne wyjścia DRM/KMS i przypisywanie okien do monitorów (dziś jeden).
48
+- **Dekoracje/zachowanie okien**: brak `xdg-decoration`, resize krawędziami, minimalizacji,
49
+ pełnego ekranu, `xdg_popup`, `layer-shell`.
50
+- **Klawiatura**: mapa `us` przez systemowe dane XKB (`/usr/share/X11/xkb`); inne układy,
51
+ IME i `repeat_info` — do zrobienia.
52
+- Schowek (`wl_data_device`), `wl_output` dynamiczne (zmiana trybu), fokus klawiatury na klik
53
+ poza oknem, SnapEdge’y/układ.
54
+- Optymalizacje: cache mapy `shm`, brak klonowania pikseli na klatkę (cache `ImageData`),
55
+ render tylko przy zmianie modelu. Backend sprzętowy (DRM/KMS + GLES).
47
56
48
57
## Uruchomienie / Running
49
58
@@ -61,11 +70,13 @@ katalog (renderowanie pojawi się, gdy wejdzie backend wyświetlania).
61
70
62
71
```
63
72
de/selene/src/
64
- main.rs # display, globale, gniazdo, pętla poll
65
- state.rs # stan współdzielony + model okien (współdzielony z wyjściem)
73
+ main.rs # display, globale, gniazdo, pętla poll (nasłuch + backend + magistrala)
74
+ state.rs # stan: model okien, z-order, fokus, drag, monitory
66
75
compositor.rs # wl_compositor / wl_surface / wl_region / wl_callback + kompozycja
67
76
shm.rs # wl_shm / wl_shm_pool / wl_buffer (+ odczyt pikseli)
68
77
shell.rs # xdg_wm_base / xdg_surface / xdg_toplevel / xdg_positioner
69
- seat.rs # wl_seat (minimalnie)
78
+ monitors.rs # wl_output (model wielu monitorów)
79
+ seat.rs # wl_seat: wskaźnik + klawiatura + trasowanie wejścia
80
+ input.rs # magistrala wejścia (kolejka + wybudzanie)
70
81
output.rs # wyjście nested (libluna-platform + libluna-render)
71
82
```
@@ -108,7 +108,7 @@ impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
108
108
fn request(
109
109
state: &mut Self,
110
110
_client: &Client,
111
- _resource: &wl_surface::WlSurface,
111
+ resource: &wl_surface::WlSurface,
112
112
request: wl_surface::Request,
113
113
data: &SurfaceData,
114
114
_handle: &DisplayHandle,
@@ -131,7 +131,7 @@ impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
131
131
// Pierwszy commit powierzchni z `xdg_surface` → wyślij konfigurację.
132
132
maybe_configure_xdg(state, data);
133
133
// Skomponuj okno (jeśli to toplevel z buforem).
134
- composite(state, data);
134
+ composite(state, resource, data);
135
135
}
136
136
_ => {}
137
137
}
@@ -139,7 +139,7 @@ impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
139
139
}
140
140
141
141
/// Skomponuj powierzchnię do modelu okien, jeśli jest to toplevel z buforem `wl_shm`.
142
-fn composite(state: &mut State, data: &SurfaceData) {
142
+fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceData) {
143
143
let Some(xdg_surface) = data.xdg_surface.lock().unwrap().clone() else {
144
144
return;
145
145
};
@@ -161,9 +161,11 @@ fn composite(state: &mut State, data: &SurfaceData) {
161
161
return;
162
162
};
163
163
let image = std::sync::Arc::new(ImageData::new(w as u32, h as u32, rgba));
164
+ let z = state.next_z();
164
165
165
166
let mut slot = data.slot.lock().unwrap();
166
167
let mut windows = state.windows.lock().unwrap();
168
+ let mut created = false;
167
169
let idx = match *slot {
168
170
Some(i) => i,
169
171
None => {
@@ -173,9 +175,11 @@ fn composite(state: &mut State, data: &SurfaceData) {
173
175
y: 60.0 + i as f64 * 36.0,
174
176
width: w,
175
177
height: h,
178
+ z,
176
179
data: image.clone(),
177
180
});
178
181
*slot = Some(i);
182
+ created = true;
179
183
i
180
184
}
181
185
};
@@ -186,6 +190,10 @@ fn composite(state: &mut State, data: &SurfaceData) {
186
190
}
187
191
drop(windows);
188
192
drop(slot);
193
+ // Zarejestruj powierzchnię okna (indeks = slot) do trasowania wejścia.
194
+ if created && state.slots.len() == idx {
195
+ state.slots.push(surface.clone());
196
+ }
189
197
// Pozwól klientowi ponownie użyć bufora.
190
198
let _ = buffer.send_event(wl_buffer::Event::Release);
191
199
}
192
200
new file mode 100644
@@ -0,0 +1,37 @@
1
+//! Magistrala wejścia: kolejka zdarzeń + para gniazd do wybudzania pętli głównej.
2
+//!
3
+//! Wątek wyjścia (osobne połączenie Wayland) odbiera zdarzenia wejścia od nadrzędnego
4
+//! kompozytora i przekazuje je tutaj; wątek protokołu opróżnia kolejkę i trasuje je do
5
+//! klientów (`wl_pointer`/`wl_keyboard`).
6
+
7
+use std::io::{Read, Write};
8
+use std::os::unix::net::UnixStream;
9
+use std::sync::{Arc, Mutex};
10
+
11
+use libluna_core::Event;
12
+
13
+/// Współdzielona kolejka zdarzeń wejścia.
14
+pub type Events = Arc<Mutex<Vec<Event>>>;
15
+
16
+/// Tworzy kolejkę i parę gniazd `(read, write)` do wybudzania.
17
+pub fn channel() -> (Events, UnixStream, UnixStream) {
18
+ let (read, write) = UnixStream::pair().expect("socketpair wejścia");
19
+ (Arc::new(Mutex::new(Vec::new())), read, write)
20
+}
21
+
22
+/// Dokłada partię zdarzeń i budzi pętlę główną (jeden bajt).
23
+pub fn push(events: &Events, wake: &mut UnixStream, batch: &[Event]) {
24
+ if batch.is_empty() {
25
+ return;
26
+ }
27
+ events.lock().unwrap().extend_from_slice(batch);
28
+ let _ = wake.write_all(&[1u8]);
29
+}
30
+
31
+/// Opróżnia bajt wybudzający (nieblokująco).
32
+pub fn drain_wake(wake: &mut UnixStream) {
33
+ let _ = wake.set_nonblocking(true);
34
+ let mut buf = [0u8; 64];
35
+ let _ = wake.read(&mut buf);
36
+ let _ = wake.set_nonblocking(false);
37
+}
@@ -1,10 +1,13 @@
1
1
//! Selene — kompozytor Wayland środowiska Luna.
2
2
//!
3
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`.
4
+//! (rdzeń + xdg-shell + output + seat), a pętla zdarzeń to bezpośredni `poll` po gnieździe
5
+//! nasłuchu, deskryptorze backendu i magistrali wejścia. Wyjście (nested) i kompozycja
6
+//! idą przez `libluna-platform` + `libluna-render`.
6
7
7
8
mod compositor;
9
+mod input;
10
+mod monitors;
8
11
mod output;
9
12
mod seat;
10
13
mod shell;
@@ -15,8 +18,9 @@ use std::error::Error;
15
18
use std::os::fd::{AsRawFd, BorrowedFd};
16
19
use std::sync::Arc;
17
20
21
+use libluna_core::Event;
18
22
use wayland_protocols::xdg::shell::server::xdg_wm_base;
19
-use wayland_server::protocol::{wl_compositor, wl_seat, wl_shm};
23
+use wayland_server::protocol::{wl_compositor, wl_output, wl_seat, wl_shm};
20
24
use wayland_server::{Display, ListeningSocket};
21
25
22
26
use state::State;
@@ -32,6 +36,7 @@ fn main() -> Result<(), Box<dyn Error>> {
32
36
// Globalne interfejsy protokołu.
33
37
handle.create_global::<State, wl_compositor::WlCompositor, ()>(4, ());
34
38
handle.create_global::<State, wl_shm::WlShm, ()>(1, ());
39
+ handle.create_global::<State, wl_output::WlOutput, ()>(4, ());
35
40
handle.create_global::<State, xdg_wm_base::XdgWmBase, ()>(1, ());
36
41
handle.create_global::<State, wl_seat::WlSeat, ()>(1, ());
37
42
@@ -49,22 +54,36 @@ fn main() -> Result<(), Box<dyn Error>> {
49
54
50
55
let mut state = State::default();
51
56
57
+ // Magistrala wejścia (wątek wyjścia → wątek protokołu) z parą gniazd do wybudzania.
58
+ let (events, mut wake_read, wake_write) = input::channel();
59
+
52
60
// Wyjście nested: okno na nadrzędnym kompozytorze, budowane w osobnym wątku.
53
- output::spawn(std::sync::Arc::clone(&state.windows), 1280, 800);
61
+ output::spawn(
62
+ Arc::clone(&state.windows),
63
+ Arc::clone(&state.outputs),
64
+ Arc::clone(&events),
65
+ wake_write,
66
+ 1280,
67
+ 800,
68
+ );
54
69
55
70
let listener_fd = socket.as_raw_fd();
56
71
57
72
loop {
58
73
let backend_fd = display.backend().poll_fd().as_raw_fd();
74
+ let wake_fd = wake_read.as_raw_fd();
59
75
60
76
let listener = unsafe { BorrowedFd::borrow_raw(listener_fd) };
61
77
let backend = unsafe { BorrowedFd::borrow_raw(backend_fd) };
78
+ let wake = unsafe { BorrowedFd::borrow_raw(wake_fd) };
62
79
let mut fds = [
63
80
rustix::event::PollFd::new(&listener, rustix::event::PollFlags::IN),
64
81
rustix::event::PollFd::new(&backend, rustix::event::PollFlags::IN),
82
+ rustix::event::PollFd::new(&wake, rustix::event::PollFlags::IN),
65
83
];
66
84
rustix::event::poll(&mut fds, -1)?;
67
85
86
+ // Nowy klient.
68
87
if fds[0].revents().contains(rustix::event::PollFlags::IN) {
69
88
while let Ok(Some(stream)) = socket.accept() {
70
89
handle.insert_client(stream, Arc::new(()))?;
@@ -72,6 +91,18 @@ fn main() -> Result<(), Box<dyn Error>> {
72
91
}
73
92
}
74
93
94
+ // Wejście z magistrali → trasowanie do klientów.
95
+ if fds[2].revents().contains(rustix::event::PollFlags::IN) {
96
+ input::drain_wake(&mut wake_read);
97
+ let batch: Vec<Event> = {
98
+ let mut queue = events.lock().unwrap();
99
+ std::mem::take(&mut *queue)
100
+ };
101
+ for event in &batch {
102
+ seat::handle_input(&mut state, event);
103
+ }
104
+ }
105
+
75
106
display.dispatch_clients(&mut state)?;
76
107
display.flush_clients()?;
77
108
}
78
109
new file mode 100644
@@ -0,0 +1,90 @@
1
+//! `wl_output` — monitory (na razie jeden; model gotowy na wiele).
2
+
3
+use std::sync::{Arc, Mutex};
4
+
5
+use wayland_server::protocol::wl_output::{self, WlOutput};
6
+use wayland_server::{Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource};
7
+
8
+use crate::state::State;
9
+
10
+/// Informacje o monitorze (współdzielone z wątkiem wyjścia, który zna bieżący rozmiar).
11
+#[derive(Debug, Clone)]
12
+pub struct OutputInfo {
13
+ pub name: String,
14
+ pub width: i32,
15
+ pub height: i32,
16
+ pub scale: i32,
17
+}
18
+
19
+impl Default for OutputInfo {
20
+ fn default() -> Self {
21
+ Self {
22
+ name: "nested-0".to_owned(),
23
+ width: 1280,
24
+ height: 800,
25
+ scale: 1,
26
+ }
27
+ }
28
+}
29
+
30
+/// Współdzielona lista monitorów.
31
+pub type Outputs = Arc<Mutex<Vec<OutputInfo>>>;
32
+
33
+/// Wysyła klientowi opis monitora (geometria, tryb, skala, nazwa, gotowe).
34
+pub fn send_info(output: &WlOutput, info: &OutputInfo) {
35
+ let _ = output.send_event(wl_output::Event::Geometry {
36
+ x: 0,
37
+ y: 0,
38
+ physical_width: info.width * 300 / 1000,
39
+ physical_height: info.height * 300 / 1000,
40
+ subpixel: wayland_server::WEnum::Value(wl_output::Subpixel::Unknown),
41
+ make: "Luna".to_owned(),
42
+ model: info.name.clone(),
43
+ transform: wayland_server::WEnum::Value(wl_output::Transform::Normal),
44
+ });
45
+ let _ = output.send_event(wl_output::Event::Mode {
46
+ flags: wayland_server::WEnum::Value(wl_output::Mode::Current),
47
+ width: info.width,
48
+ height: info.height,
49
+ refresh: 60_000,
50
+ });
51
+ let _ = output.send_event(wl_output::Event::Scale { factor: info.scale });
52
+ let _ = output.send_event(wl_output::Event::Name {
53
+ name: info.name.clone(),
54
+ });
55
+ let _ = output.send_event(wl_output::Event::Done);
56
+}
57
+
58
+impl GlobalDispatch<WlOutput, ()> for State {
59
+ fn bind(
60
+ state: &mut Self,
61
+ _handle: &DisplayHandle,
62
+ _client: &Client,
63
+ resource: New<WlOutput>,
64
+ _data: &(),
65
+ data_init: &mut DataInit<'_, Self>,
66
+ ) {
67
+ let output = data_init.init(resource, ());
68
+ let info = state
69
+ .outputs
70
+ .lock()
71
+ .unwrap()
72
+ .first()
73
+ .cloned()
74
+ .unwrap_or_default();
75
+ send_info(&output, &info);
76
+ }
77
+}
78
+
79
+impl Dispatch<WlOutput, ()> for State {
80
+ fn request(
81
+ _state: &mut Self,
82
+ _client: &Client,
83
+ _resource: &WlOutput,
84
+ _request: wl_output::Request,
85
+ _data: &(),
86
+ _handle: &DisplayHandle,
87
+ _data_init: &mut DataInit<'_, Self>,
88
+ ) {
89
+ }
90
+}
@@ -1,23 +1,46 @@
1
1
//! Wyjście nested: Selene otwiera okno na nadrzędnym kompozytorze i rysuje scenę okien.
2
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.
3
+//! Wątek wyjścia pełni dwie role: (1) rysuje skomponowaną scenę przez `libluna-render`
4
+//! i (2) odbiera wejście od nadrzędnego kompozytora, przekazując je do magistrali
5
+//! (`crate::input`), skąd wątek protokołu trasuje je do klientów.
6
+
7
+use std::os::unix::net::UnixStream;
6
8
7
9
use libluna_core::{Color, Point, Rect, ThemeSettings};
8
10
use libluna_platform::{run, AppConfig, Frame};
9
11
use libluna_render::TextRun;
10
12
11
-use crate::state::WindowModel;
13
+use crate::input::{self, Events};
14
+use crate::monitors::Outputs;
15
+use crate::state::{Window, WindowModel};
12
16
13
17
/// Uruchamia wątek wyjścia (nested Wayland) o zadanym rozmiarze.
14
-pub fn spawn(model: WindowModel, width: u32, height: u32) {
18
+pub fn spawn(
19
+ model: WindowModel,
20
+ outputs: Outputs,
21
+ events: Events,
22
+ wake: UnixStream,
23
+ width: u32,
24
+ height: u32,
25
+) {
15
26
std::thread::spawn(move || {
27
+ let mut wake = wake;
16
28
let config = AppConfig::new("Selene")
17
29
.app_id("eu.paganlinux.Luna.Selene")
18
30
.initial_size(width, height)
19
31
.settings(ThemeSettings::default());
20
32
let _ = run(config, move |frame: &mut Frame| {
33
+ // Zgłoś bieżący rozmiar monitora (okno może być zmieniane przez rodzica).
34
+ {
35
+ let mut outs = outputs.lock().unwrap();
36
+ if let Some(out) = outs.first_mut() {
37
+ out.width = frame.size.width as i32;
38
+ out.height = frame.size.height as i32;
39
+ }
40
+ }
41
+ // Przekaż wejście do magistrali.
42
+ input::push(&events, &mut wake, frame.events);
43
+ // Narysuj scenę.
21
44
draw(frame, &model);
22
45
// Utrzymuj klatki — model okien zmienia się poza tą pętlą.
23
46
frame.request_frame();
@@ -26,16 +49,17 @@ pub fn spawn(model: WindowModel, width: u32, height: u32) {
26
49
}
27
50
28
51
fn draw(frame: &mut Frame, model: &WindowModel) {
29
- let size = frame.size;
30
52
frame.scene.clear();
31
53
frame.scene.fill_rect(
32
- Rect::from_size(size),
54
+ Rect::from_size(frame.size),
33
55
Color::from_rgba8(0x1e, 0x20, 0x28, 0xff),
34
56
);
35
57
36
58
{
37
59
let windows = model.lock().unwrap();
38
- for w in windows.iter() {
60
+ let mut order: Vec<&Window> = windows.iter().collect();
61
+ order.sort_by_key(|w| w.z);
62
+ for w in order {
39
63
frame.scene.image(
40
64
Rect::from_xywh(w.x, w.y, w.width as f64, w.height as f64),
41
65
w.data.clone(),
@@ -1,11 +1,29 @@
1
-//! `wl_seat` — minimalne wejście (na razie bez zdarzeń; obiekty tworzone, by klienci działali).
1
+//! `wl_seat` — wejście: wskaźnik (`wl_pointer`), klawiatura (`wl_keyboard`) i trasowanie.
2
+//!
3
+//! Zdarzenia wejścia pochodzą z magistrali (`crate::input`) — przekazuje je wątek wyjścia,
4
+//! który odbiera je od nadrzędnego kompozytora. Tutaj tłumaczymy je na zdarzenia protokołu
5
+//! wysyłane do sfokusowanych powierzchni klientów.
2
6
7
+use std::io::{Seek, Write};
8
+use std::os::fd::{AsFd, OwnedFd};
9
+
10
+use libluna_core::{ButtonState, Event, Modifiers, Point, PointerButton};
11
+use wayland_server::protocol::{wl_keyboard, wl_pointer, wl_seat, wl_touch};
3
12
use wayland_server::{
4
- protocol::{wl_keyboard, wl_pointer, wl_seat, wl_touch},
5
- Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
13
+ Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
6
14
};
7
15
8
-use crate::state::State;
16
+use crate::state::{DragState, State};
17
+
18
+/// Kompaktowa mapa klawiszy (standardowe reguły XKB z systemowych danych).
19
+const KEYMAP: &str = r#"xkb_keymap {
20
+ xkb_keycodes { include "evdev+aliases(qwerty)" };
21
+ xkb_types { include "complete" };
22
+ xkb_compat { include "complete" };
23
+ xkb_symbols { include "pc+us+inet(evdev)" };
24
+ xkb_geometry { include "pc(pc105)" };
25
+};
26
+"#;
9
27
10
28
impl GlobalDispatch<wl_seat::WlSeat, ()> for State {
11
29
fn bind(
@@ -17,15 +35,16 @@ impl GlobalDispatch<wl_seat::WlSeat, ()> for State {
17
35
data_init: &mut DataInit<'_, Self>,
18
36
) {
19
37
let seat = data_init.init(resource, ());
38
+ let caps = wl_seat::Capability::Pointer | wl_seat::Capability::Keyboard;
20
39
let _ = seat.send_event(wl_seat::Event::Capabilities {
21
- capabilities: wayland_server::WEnum::Value(wl_seat::Capability::empty()),
40
+ capabilities: WEnum::Value(caps),
22
41
});
23
42
}
24
43
}
25
44
26
45
impl Dispatch<wl_seat::WlSeat, ()> for State {
27
46
fn request(
28
- _state: &mut Self,
47
+ state: &mut Self,
29
48
_client: &Client,
30
49
_resource: &wl_seat::WlSeat,
31
50
request: wl_seat::Request,
@@ -35,10 +54,18 @@ impl Dispatch<wl_seat::WlSeat, ()> for State {
35
54
) {
36
55
match request {
37
56
wl_seat::Request::GetPointer { id } => {
38
- data_init.init(id, ());
57
+ state.pointer = Some(data_init.init(id, ()));
39
58
}
40
59
wl_seat::Request::GetKeyboard { id } => {
41
- data_init.init(id, ());
60
+ let kb = data_init.init(id, ());
61
+ if let Some((fd, size)) = make_keymap() {
62
+ let _ = kb.send_event(wl_keyboard::Event::Keymap {
63
+ format: WEnum::Value(wl_keyboard::KeymapFormat::XkbV1),
64
+ fd: fd.as_fd(),
65
+ size,
66
+ });
67
+ }
68
+ state.keyboard = Some(kb);
42
69
}
43
70
wl_seat::Request::GetTouch { id } => {
44
71
data_init.init(id, ());
@@ -68,3 +95,415 @@ macro_rules! empty_dispatch {
68
95
empty_dispatch!(wl_pointer::WlPointer);
69
96
empty_dispatch!(wl_keyboard::WlKeyboard);
70
97
empty_dispatch!(wl_touch::WlTouch);
98
+
99
+/// Buduje deskryptor z mapą klawiszy (memfd + tekst XKB, zakończony NUL).
100
+fn make_keymap() -> Option<(OwnedFd, u32)> {
101
+ let fd = rustix::fs::memfd_create("selene-keymap", rustix::fs::MemfdFlags::CLOEXEC).ok()?;
102
+ let mut file = std::fs::File::from(fd);
103
+ let mut bytes = KEYMAP.as_bytes().to_vec();
104
+ bytes.push(0);
105
+ file.write_all(&bytes).ok()?;
106
+ file.rewind().ok()?;
107
+ Some((OwnedFd::from(file), bytes.len() as u32))
108
+}
109
+
110
+// --- Trasowanie wejścia ---
111
+
112
+/// Trasuje jedno zdarzenie wejścia do klientów.
113
+pub fn handle_input(state: &mut State, event: &Event) {
114
+ match event {
115
+ Event::PointerMotion { position, .. } => pointer_motion(state, *position),
116
+ Event::PointerButton(pe) => pointer_button(state, pe.position, pe.button, pe.state),
117
+ Event::PointerLeave => pointer_leave(state),
118
+ Event::Scroll(se) => pointer_axis(state, se.dx, se.dy),
119
+ Event::Key(ke) => keyboard_key(state, ke.keysym, ke.state),
120
+ _ => {}
121
+ }
122
+}
123
+
124
+/// Ostatnio przeniesiona pozycja kursora (do trasowania bez `motion`).
125
+fn topmost_window(state: &State, pos: Point) -> Option<usize> {
126
+ let windows = state.windows.lock().unwrap();
127
+ let mut best: Option<(usize, i32)> = None;
128
+ for (i, w) in windows.iter().enumerate() {
129
+ if pos.x >= w.x
130
+ && pos.x < w.x + w.width as f64
131
+ && pos.y >= w.y
132
+ && pos.y < w.y + w.height as f64
133
+ {
134
+ match best {
135
+ Some((_, z)) if z >= w.z => {}
136
+ _ => best = Some((i, w.z)),
137
+ }
138
+ }
139
+ }
140
+ best.map(|(i, _)| i)
141
+}
142
+
143
+fn window_origin(state: &State, slot: usize) -> (f64, f64) {
144
+ let windows = state.windows.lock().unwrap();
145
+ windows.get(slot).map(|w| (w.x, w.y)).unwrap_or((0.0, 0.0))
146
+}
147
+
148
+fn pointer_motion(state: &mut State, pos: Point) {
149
+ // Przeciąganie okna — przesuń je razem z kursorem.
150
+ if let Some(drag) = state.drag {
151
+ let mut windows = state.windows.lock().unwrap();
152
+ if let Some(w) = windows.get_mut(drag.slot) {
153
+ w.x = pos.x - drag.off_x;
154
+ w.y = pos.y - drag.off_y;
155
+ }
156
+ }
157
+ update_pointer_focus(state, pos);
158
+ if let Some((slot, _)) = state.pointer_focus.clone() {
159
+ let ptr = state.pointer.clone();
160
+ let (ox, oy) = window_origin(state, slot);
161
+ let time = state.time_ms();
162
+ if let Some(ptr) = ptr {
163
+ let _ = ptr.send_event(wl_pointer::Event::Motion {
164
+ time,
165
+ surface_x: pos.x - ox,
166
+ surface_y: pos.y - oy,
167
+ });
168
+ }
169
+ }
170
+}
171
+
172
+fn pointer_button(state: &mut State, pos: Point, button: PointerButton, st: ButtonState) {
173
+ update_pointer_focus(state, pos);
174
+ if st == ButtonState::Pressed {
175
+ if let Some(slot) = topmost_window(state, pos) {
176
+ raise_and_focus(state, slot);
177
+ }
178
+ }
179
+ let focused = state.pointer_focus.clone();
180
+ let ptr = state.pointer.clone();
181
+ if let (Some((slot, _)), Some(ptr)) = (focused, ptr) {
182
+ let serial = state.next_serial();
183
+ let time = state.time_ms();
184
+ let wl_state = match st {
185
+ ButtonState::Pressed => WEnum::Value(wl_pointer::ButtonState::Pressed),
186
+ ButtonState::Released => WEnum::Value(wl_pointer::ButtonState::Released),
187
+ };
188
+ let _ = ptr.send_event(wl_pointer::Event::Button {
189
+ serial,
190
+ time,
191
+ button: button_code(button),
192
+ state: wl_state,
193
+ });
194
+ // Lewy przycisk: początek/koniec przeciągania okna.
195
+ if button == PointerButton::Left {
196
+ match st {
197
+ ButtonState::Pressed => {
198
+ let (ox, oy) = window_origin(state, slot);
199
+ state.drag = Some(DragState {
200
+ slot,
201
+ off_x: pos.x - ox,
202
+ off_y: pos.y - oy,
203
+ });
204
+ }
205
+ ButtonState::Released => state.drag = None,
206
+ }
207
+ }
208
+ }
209
+}
210
+
211
+fn pointer_leave(state: &mut State) {
212
+ let ptr = state.pointer.clone();
213
+ if let (Some((_, surface)), Some(ptr)) = (state.pointer_focus.clone(), ptr) {
214
+ let serial = state.next_serial();
215
+ let _ = ptr.send_event(wl_pointer::Event::Leave { serial, surface });
216
+ }
217
+ state.pointer_focus = None;
218
+}
219
+
220
+fn pointer_axis(state: &mut State, dx: f64, dy: f64) {
221
+ let ptr = state.pointer.clone();
222
+ if let (true, Some(ptr)) = (state.pointer_focus.is_some(), ptr) {
223
+ let time = state.time_ms();
224
+ if dy != 0.0 {
225
+ let _ = ptr.send_event(wl_pointer::Event::Axis {
226
+ time,
227
+ axis: WEnum::Value(wl_pointer::Axis::VerticalScroll),
228
+ value: dy,
229
+ });
230
+ }
231
+ if dx != 0.0 {
232
+ let _ = ptr.send_event(wl_pointer::Event::Axis {
233
+ time,
234
+ axis: WEnum::Value(wl_pointer::Axis::HorizontalScroll),
235
+ value: dx,
236
+ });
237
+ }
238
+ }
239
+}
240
+
241
+fn update_pointer_focus(state: &mut State, pos: Point) {
242
+ let target = topmost_window(state, pos);
243
+ let same = matches!((state.pointer_focus.as_ref(), target), (Some((s, _)), Some(t)) if *s == t);
244
+ if same {
245
+ return;
246
+ }
247
+ // Opuść poprzednią powierzchnię.
248
+ let ptr = state.pointer.clone();
249
+ if let (Some((_, surface)), Some(ptr)) = (state.pointer_focus.clone(), ptr.clone()) {
250
+ let serial = state.next_serial();
251
+ let _ = ptr.send_event(wl_pointer::Event::Leave { serial, surface });
252
+ }
253
+ state.pointer_focus = None;
254
+ // Wejdź na nową.
255
+ if let Some(slot) = target {
256
+ if let Some(surface) = state.slots.get(slot).cloned() {
257
+ let serial = state.next_serial();
258
+ let (ox, oy) = window_origin(state, slot);
259
+ if let Some(ptr) = ptr {
260
+ let _ = ptr.send_event(wl_pointer::Event::Enter {
261
+ serial,
262
+ surface: surface.clone(),
263
+ surface_x: pos.x - ox,
264
+ surface_y: pos.y - oy,
265
+ });
266
+ }
267
+ state.pointer_focus = Some((slot, surface));
268
+ }
269
+ }
270
+}
271
+
272
+fn raise_and_focus(state: &mut State, slot: usize) {
273
+ let z = state.next_z();
274
+ {
275
+ let mut windows = state.windows.lock().unwrap();
276
+ if let Some(w) = windows.get_mut(slot) {
277
+ w.z = z;
278
+ }
279
+ }
280
+ set_keyboard_focus(state, slot);
281
+}
282
+
283
+fn set_keyboard_focus(state: &mut State, slot: usize) {
284
+ if state.keyboard_focus.as_ref().map(|(s, _)| *s) == Some(slot) {
285
+ return;
286
+ }
287
+ let kb = state.keyboard.clone();
288
+ if let (Some((_, surface)), Some(kb)) = (state.keyboard_focus.clone(), kb.clone()) {
289
+ let serial = state.next_serial();
290
+ let _ = kb.send_event(wl_keyboard::Event::Leave { serial, surface });
291
+ }
292
+ state.keyboard_focus = None;
293
+ if let Some(surface) = state.slots.get(slot).cloned() {
294
+ if let Some(kb) = kb {
295
+ let serial = state.next_serial();
296
+ let _ = kb.send_event(wl_keyboard::Event::Enter {
297
+ serial,
298
+ surface: surface.clone(),
299
+ keys: vec![],
300
+ });
301
+ }
302
+ state.keyboard_focus = Some((slot, surface));
303
+ }
304
+}
305
+
306
+fn keyboard_key(state: &mut State, keysym: u32, st: ButtonState) {
307
+ if state.keyboard_focus.is_none() {
308
+ return;
309
+ }
310
+ let kb = state.keyboard.clone();
311
+ let Some(kb) = kb else { return };
312
+ // Modyfikatory: synchronizuj wciśnięcia Shift/Ctrl/Alt/Super.
313
+ sync_modifiers(state, &kb, keysym, st);
314
+ if is_modifier_keysym(keysym) {
315
+ return;
316
+ }
317
+ if let Some(key) = keysym_to_keycode(keysym) {
318
+ let serial = state.next_serial();
319
+ let time = state.time_ms();
320
+ let wl_state = match st {
321
+ ButtonState::Pressed => WEnum::Value(wl_keyboard::KeyState::Pressed),
322
+ ButtonState::Released => WEnum::Value(wl_keyboard::KeyState::Released),
323
+ };
324
+ let _ = kb.send_event(wl_keyboard::Event::Key {
325
+ serial,
326
+ time,
327
+ key,
328
+ state: wl_state,
329
+ });
330
+ }
331
+}
332
+
333
+/// Wysyła wciśnięcia/zwolnienia klawiszy modyfikujących wynikające z bieżącego stanu.
334
+///
335
+/// `keysym`/`st` opisują zdarzenie, które przyszło; aktualny stan modyfikatorów wyliczamy
336
+/// z samego zdarzenia (nacisk modyfikatora) — dla liter to wystarcza w praktyce.
337
+fn sync_modifiers(state: &mut State, kb: &wl_keyboard::WlKeyboard, keysym: u32, st: ButtonState) {
338
+ let mut mods = state.sent_mods;
339
+ let target = modifier_for_keysym(keysym);
340
+ if let Some(field) = target {
341
+ match st {
342
+ ButtonState::Pressed => *field(&mut mods) = true,
343
+ ButtonState::Released => *field(&mut mods) = false,
344
+ }
345
+ }
346
+ // Wyślij różnice jako wciśnięcia/zwolnienia konkretnych klawiszy.
347
+ for (before, now, code) in [
348
+ (state.sent_mods.shift, mods.shift, 50u32),
349
+ (state.sent_mods.ctrl, mods.ctrl, 37),
350
+ (state.sent_mods.alt, mods.alt, 64),
351
+ (state.sent_mods.logo, mods.logo, 133),
352
+ ] {
353
+ if before != now {
354
+ let serial = state.next_serial();
355
+ let time = state.time_ms();
356
+ let ks = if now {
357
+ WEnum::Value(wl_keyboard::KeyState::Pressed)
358
+ } else {
359
+ WEnum::Value(wl_keyboard::KeyState::Released)
360
+ };
361
+ let _ = kb.send_event(wl_keyboard::Event::Key {
362
+ serial,
363
+ time,
364
+ key: code,
365
+ state: ks,
366
+ });
367
+ }
368
+ }
369
+ if mods != state.sent_mods {
370
+ state.sent_mods = mods;
371
+ let serial = state.next_serial();
372
+ let _ = kb.send_event(wl_keyboard::Event::Modifiers {
373
+ serial,
374
+ mods_depressed: modifier_mask(mods),
375
+ mods_latched: 0,
376
+ mods_locked: 0,
377
+ group: 0,
378
+ });
379
+ }
380
+}
381
+
382
+fn modifier_for_keysym(keysym: u32) -> Option<fn(&mut Modifiers) -> &mut bool> {
383
+ match keysym {
384
+ 0xffe1 | 0xffe2 => Some(|m| &mut m.shift), // Shift_L / Shift_R
385
+ 0xffe3 | 0xffe4 => Some(|m| &mut m.ctrl), // Control_L / Control_R
386
+ 0xffe9 | 0xffea => Some(|m| &mut m.alt), // Alt_L / Alt_R
387
+ 0xffeb | 0xffec => Some(|m| &mut m.logo), // Super_L / Super_R
388
+ _ => None,
389
+ }
390
+}
391
+
392
+fn is_modifier_keysym(keysym: u32) -> bool {
393
+ modifier_for_keysym(keysym).is_some()
394
+}
395
+
396
+fn modifier_mask(m: Modifiers) -> u32 {
397
+ (m.shift as u32) | ((m.ctrl as u32) << 2) | ((m.alt as u32) << 3) | ((m.logo as u32) << 6)
398
+}
399
+
400
+fn button_code(button: PointerButton) -> u32 {
401
+ match button {
402
+ PointerButton::Left => 0x110,
403
+ PointerButton::Right => 0x111,
404
+ PointerButton::Middle => 0x112,
405
+ PointerButton::Back => 0x116,
406
+ PointerButton::Forward => 0x115,
407
+ PointerButton::Other(c) => c,
408
+ }
409
+}
410
+
411
+/// Keysym → kod klawisza XKB (evdev + 8) dla typowych klawiszy.
412
+fn keysym_to_keycode(keysym: u32) -> Option<u32> {
413
+ let code = match keysym {
414
+ 0x0020 => 65, // space
415
+ 0xff0d => 36, // Return
416
+ 0xff1b => 9, // Escape
417
+ 0xff08 => 22, // BackSpace
418
+ 0xff09 => 23, // Tab
419
+ 0xffff => 119, // Delete
420
+ 0xff50 => 110, // Home
421
+ 0xff57 => 115, // End
422
+ 0xff51 => 113, // Left
423
+ 0xff52 => 111, // Up
424
+ 0xff53 => 114, // Right
425
+ 0xff54 => 116, // Down
426
+ // litery (małe)
427
+ 0x0061 => 38,
428
+ 0x0062 => 56,
429
+ 0x0063 => 54,
430
+ 0x0064 => 40,
431
+ 0x0065 => 26,
432
+ 0x0066 => 41,
433
+ 0x0067 => 42,
434
+ 0x0068 => 43,
435
+ 0x0069 => 31,
436
+ 0x006a => 44,
437
+ 0x006b => 45,
438
+ 0x006c => 46,
439
+ 0x006d => 58,
440
+ 0x006e => 57,
441
+ 0x006f => 32,
442
+ 0x0070 => 33,
443
+ 0x0071 => 24,
444
+ 0x0072 => 27,
445
+ 0x0073 => 39,
446
+ 0x0074 => 28,
447
+ 0x0075 => 30,
448
+ 0x0076 => 55,
449
+ 0x0077 => 25,
450
+ 0x0078 => 53,
451
+ 0x0079 => 29,
452
+ 0x007a => 52,
453
+ // cyfry
454
+ 0x0031 => 10,
455
+ 0x0032 => 11,
456
+ 0x0033 => 12,
457
+ 0x0034 => 13,
458
+ 0x0035 => 14,
459
+ 0x0036 => 15,
460
+ 0x0037 => 16,
461
+ 0x0038 => 17,
462
+ 0x0039 => 18,
463
+ 0x0030 => 19,
464
+ // interpunkcja
465
+ 0x002d => 20,
466
+ 0x003d => 21,
467
+ 0x005b => 34,
468
+ 0x005d => 35,
469
+ 0x003b => 47,
470
+ 0x0027 => 48,
471
+ 0x0060 => 49,
472
+ 0x002c => 59,
473
+ 0x002e => 60,
474
+ 0x002f => 61,
475
+ 0x005c => 51,
476
+ _ => return None,
477
+ };
478
+ Some(code)
479
+}
480
+
481
+#[cfg(test)]
482
+mod tests {
483
+ use super::*;
484
+
485
+ #[test]
486
+ fn maps_common_keys() {
487
+ assert_eq!(keysym_to_keycode(0x0061), Some(38)); // a
488
+ assert_eq!(keysym_to_keycode(0xff0d), Some(36)); // Enter
489
+ assert_eq!(keysym_to_keycode(0xff1b), Some(9)); // Escape
490
+ assert_eq!(keysym_to_keycode(0x9999), None);
491
+ }
492
+
493
+ #[test]
494
+ fn modifier_mask_matches_xkb() {
495
+ let m = Modifiers {
496
+ shift: true,
497
+ ctrl: true,
498
+ alt: false,
499
+ logo: true,
500
+ };
501
+ assert_eq!(modifier_mask(m), 1 | 4 | 64);
502
+ }
503
+
504
+ #[test]
505
+ fn recognizes_modifier_keysyms() {
506
+ assert!(is_modifier_keysym(0xffe1)); // Shift_L
507
+ assert!(!is_modifier_keysym(0x0061));
508
+ }
509
+}
@@ -3,14 +3,22 @@
3
3
use std::sync::{Arc, Mutex};
4
4
use std::time::Instant;
5
5
6
+use libluna_core::Modifiers;
6
7
use libluna_render::ImageData;
8
+use wayland_server::protocol::wl_keyboard::WlKeyboard;
9
+use wayland_server::protocol::wl_pointer::WlPointer;
10
+use wayland_server::protocol::wl_surface::WlSurface;
7
11
8
-/// Okno (toplevel) w modelu kompozycji: pozycja, rozmiar i gotowe piksele.
12
+use crate::monitors::{OutputInfo, Outputs};
13
+
14
+/// Okno (toplevel) w modelu kompozycji: pozycja, rozmiar, kolejność i gotowe piksele.
9
15
pub struct Window {
10
16
pub x: f64,
11
17
pub y: f64,
12
18
pub width: i32,
13
19
pub height: i32,
20
+ /// Kolejność (wyższa = wyżej); do renderu i trafień.
21
+ pub z: i32,
14
22
/// Piksele (premultiplied RGBA8) ostatnio zacommitowanego bufora.
15
23
pub data: Arc<ImageData>,
16
24
}
@@ -18,18 +26,43 @@ pub struct Window {
18
26
/// Współdzielony model okien (pisany przez wątek protokołu, czytany przez wyjście).
19
27
pub type WindowModel = Arc<Mutex<Vec<Window>>>;
20
28
29
+/// Trwające przeciąganie okna.
30
+#[derive(Debug, Clone, Copy)]
31
+pub struct DragState {
32
+ pub slot: usize,
33
+ /// Przesunięcie kursora względem lewego-górnego narożnika okna w chwili wciśnięcia.
34
+ pub off_x: f64,
35
+ pub off_y: f64,
36
+}
37
+
21
38
/// Stan współdzielony przez wszystkie handlery protokołu.
22
39
pub struct State {
23
40
/// Start pracy kompozytora — do znaczników czasu (frame callbacks).
24
41
pub start: Instant,
25
42
/// Licznik seriali (dla `xdg_surface.configure` itp.).
26
43
serial: u32,
27
- /// Liczniki diagnostyczne (log startowy / debug).
44
+ /// Liczniki diagnostyczne.
28
45
pub clients: u32,
29
46
pub surfaces: u32,
30
47
pub toplevels: u32,
31
48
/// Model okien współdzielony z wątkiem wyjścia.
32
49
pub windows: WindowModel,
50
+ /// Powierzchnie okien (indeks = slot w [`State::windows`]).
51
+ pub slots: Vec<WlSurface>,
52
+ /// Licznik kolejności (z-order).
53
+ z_counter: i32,
54
+ /// Monitory (współdzielone z wątkiem wyjścia).
55
+ pub outputs: Outputs,
56
+
57
+ // — Wejście —
58
+ pub pointer: Option<WlPointer>,
59
+ pub keyboard: Option<WlKeyboard>,
60
+ pub pointer_focus: Option<(usize, WlSurface)>,
61
+ pub keyboard_focus: Option<(usize, WlSurface)>,
62
+ /// Modyfikatory, o których już powiadomiliśmy klienta klawiatury.
63
+ pub sent_mods: Modifiers,
64
+ /// Trwające przeciąganie okna.
65
+ pub drag: Option<DragState>,
33
66
}
34
67
35
68
impl Default for State {
@@ -41,6 +74,15 @@ impl Default for State {
41
74
surfaces: 0,
42
75
toplevels: 0,
43
76
windows: Arc::new(Mutex::new(Vec::new())),
77
+ slots: Vec::new(),
78
+ z_counter: 1,
79
+ outputs: Arc::new(Mutex::new(vec![OutputInfo::default()])),
80
+ pointer: None,
81
+ keyboard: None,
82
+ pointer_focus: None,
83
+ keyboard_focus: None,
84
+ sent_mods: Modifiers::NONE,
85
+ drag: None,
44
86
}
45
87
}
46
88
}
@@ -56,6 +98,12 @@ impl State {
56
98
pub fn time_ms(&self) -> u32 {
57
99
self.start.elapsed().as_millis() as u32
58
100
}
101
+
102
+ /// Kolejna wartość z-order (podnosi okno na wierzch).
103
+ pub fn next_z(&mut self) -> i32 {
104
+ self.z_counter += 1;
105
+ self.z_counter
106
+ }
59
107
}
60
108
61
109
#[cfg(test)]
@@ -72,10 +120,10 @@ mod tests {
72
120
}
73
121
74
122
#[test]
75
- fn time_ms_is_monotonic() {
76
- let s = State::default();
77
- let t0 = s.time_ms();
78
- let t1 = s.time_ms();
79
- assert!(t1 >= t0);
123
+ fn z_order_increases() {
124
+ let mut s = State::default();
125
+ let a = s.next_z();
126
+ let b = s.next_z();
127
+ assert!(b > a);
80
128
}
81
129
}
@@ -135,10 +135,13 @@ a na końcu odtworzył realne aplikacje środowiska Luna z makiet.
135
135
+ frame-callbacki, `xdg_surface.configure`, i18n, profil release z LTO.
136
136
✅ M-K1: **wyjście nested** (`libluna-platform`) + kompozycja okien klientów (`wl_shm` →
137
137
piksele → `libluna-render`); nowy prymityw `Scene::image`/`ImageData` w rendererze.
138
+ ✅ M-K2: **wejście** — `wl_output`, `wl_seat` z wskaźnikiem i klawiaturą (`wl_pointer`,
139
+ `wl_keyboard` z mapą XKB i syntezą modyfikatorów), magistrala wejścia z wybudzaniem,
140
+ oraz **pływające okna** (fokus, podnoszenie, przeciąganie).
138
141
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).
142
+- Dalej: **wiele monitorów** (DRM/KMS), dekory/zachowanie okien (`xdg-decoration`, resize,
143
+ pełny ekran, `xdg_popup`, `layer-shell`), schowek (`wl_data_device`), układy klawiatury/IME,
144
+ backend DRM/KMS + GLES, optymalizacje (cache mapy shm, render przy zmianie modelu).
142
145
- **Luna** (`de/luna`) — powłoka środowiska (panel, launcher, OSD) na `libluna-widgets`.
143
146
144
147
## Później (poza MVP)