← luna

Commit 3fab2e0

7
files
+120
added
-9
deleted
@@ -109,7 +109,9 @@ XDG_RUNTIME_DIR="$PWD/.selene-runtime" cargo run -p selene
109 109 `XDG_RUNTIME_DIR` zwykle ustawia menedżer sesji. Gdy go brak (np. `pam_systemd` nie
110 110 założył sesji), Selene wybiera katalog sam: najpierw `/run/user/<uid>`, a gdy ten nie
111 111 istnieje — tworzy prywatny katalog `0700` pod `$TMPDIR`. Skrypt `luna-session` robi ten
112 -sam wybór wcześniej, żeby zmienną dziedziczyli także klienci.
112 +sam wybór wcześniej, żeby zmienną dziedziczyli także klienci. `luna-session` uruchamia
113 +także powłokę `luna-shell` (panel/OSD), o ile jest zainstalowana; w trybie DRM Selene
114 +rysuje własny kursor (strzałkę), bo nie ma nadrzędnego kompozytora.
113 115
114 116 Log uruchomienia sesji trafia do stałego pliku `~/.local/state/luna/session.log`
115 117 (LightDM trzyma tylko dwa pliki `.xsession-errors` i obraca je przy każdej sesji, więc
@@ -99,6 +99,11 @@ impl EvdevInput {
99 99 self.devices.iter().map(|d| d.fd.as_raw_fd()).collect()
100 100 }
101 101
102 + /// Bieżąca pozycja wirtualnego kursora (współrzędne logiczne).
103 + pub fn cursor(&self) -> Point {
104 + self.cursor
105 + }
106 +
102 107 /// Czyta i tłumaczy wszystkie oczekujące zdarzenia.
103 108 ///
104 109 /// `bounds` to rozmiar monitora — kursor jest do niego ograniczany.
@@ -105,7 +105,7 @@ fn main() -> Result<(), Box<dyn Error>> {
105 105
106 106 // Wyjście: realne DRM/KMS (gdy `SELENE_BACKEND=drm`) lub nested na nadrzędnym kompozytorze.
107 107 if std::env::var("SELENE_BACKEND").as_deref() == Ok("drm") {
108 - output::spawn_drm(Arc::clone(&state.windows));
108 + output::spawn_drm(Arc::clone(&state.windows), Arc::clone(&state.cursor));
109 109 } else {
110 110 output::spawn(
111 111 Arc::clone(&state.windows),
@@ -172,6 +172,7 @@ fn main() -> Result<(), Box<dyn Error>> {
172 172 .map(|o| (o.width as f64, o.height as f64))
173 173 .unwrap_or((1280.0, 800.0));
174 174 let batch = input.read_events(bounds);
175 + *state.cursor.lock_or_recover() = Some(input.cursor());
175 176 for event in &batch {
176 177 seat::handle_input(&mut state, &handle, event);
177 178 }
@@ -15,7 +15,7 @@ use libluna_render::{Scene, SoftwareRenderer, TextRun};
15 15 use crate::drm::{DrmBackend, DrmError};
16 16 use crate::input::{self, Events};
17 17 use crate::monitors::Outputs;
18 -use crate::state::{Window, WindowModel};
18 +use crate::state::{CursorModel, Window, WindowModel};
19 19 use crate::sync::LockExt;
20 20
21 21 /// Uruchamia wątek wyjścia (nested Wayland) o zadanym rozmiarze.
@@ -46,7 +46,7 @@ pub fn spawn(
46 46 input::push(&events, &mut wake, frame.events);
47 47 // Narysuj scenę.
48 48 let size = frame.size;
49 - build_scene(frame.scene, size, 0.0, 0.0, &model);
49 + build_scene(frame.scene, size, 0.0, 0.0, &model, None);
50 50 // Utrzymuj klatki — model okien zmienia się poza tą pętlą.
51 51 frame.request_frame();
52 52 });
@@ -87,7 +87,7 @@ fn open_drm_waiting() -> Result<DrmBackend, DrmError> {
87 87 ///
88 88 /// Wymaga bycia DRM masterem; jeśli inną sesję trzyma master, kończy bez efektu
89 89 /// (wywołujący powinien wtedy użyć wyjścia nested).
90 -pub fn spawn_drm(model: WindowModel) {
90 +pub fn spawn_drm(model: WindowModel, cursor: CursorModel) {
91 91 std::thread::spawn(move || {
92 92 let mut backend = match open_drm_waiting() {
93 93 Ok(backend) => backend,
@@ -137,8 +137,16 @@ pub fn spawn_drm(model: WindowModel) {
137 137 for i in 0..sizes.len() {
138 138 let (w, h) = sizes[i];
139 139 let (ox, oy) = origins[i];
140 + let cur = *cursor.lock_or_recover();
140 141 let mut scene = Scene::new();
141 - build_scene(&mut scene, Size::new(w as f64, h as f64), ox, oy, &model);
142 + build_scene(
143 + &mut scene,
144 + Size::new(w as f64, h as f64),
145 + ox,
146 + oy,
147 + &model,
148 + cur,
149 + );
142 150 renderers[i].draw(&scene, 1.0, bg);
143 151 if !backend.present(i, renderers[i].data()) && !flip_error_logged {
144 152 eprintln!(
@@ -156,7 +164,14 @@ pub fn spawn_drm(model: WindowModel) {
156 164 }
157 165
158 166 /// Buduje scenę okien w rozmiarze wyjścia, przesuniętą o `(ox, oy)` (origin monitora).
159 -fn build_scene(scene: &mut Scene, size: Size, ox: f64, oy: f64, model: &WindowModel) {
167 +fn build_scene(
168 + scene: &mut Scene,
169 + size: Size,
170 + ox: f64,
171 + oy: f64,
172 + model: &WindowModel,
173 + cursor: Option<Point>,
174 +) {
160 175 let sh = |r: Rect| r.offset(-ox, -oy);
161 176 scene.clear();
162 177 scene.fill_rect(
@@ -232,4 +247,50 @@ fn build_scene(scene: &mut Scene, size: Size, ox: f64, oy: f64, model: &WindowMo
232 247 TextRun::new(Point::new(16.0, 28.0), "Selene — kompozytor Luna", 14.0)
233 248 .with_color(Color::from_rgba8(0xdd, 0xdd, 0xdd, 0xff)),
234 249 );
250 +
251 + // Kursor rysuje ten monitor, na którym leży (tryb DRM — nikt go za nas nie narysuje).
252 + if let Some(c) = cursor {
253 + let (cx, cy) = (c.x - ox, c.y - oy);
254 + if cx >= 0.0 && cy >= 0.0 && cx < size.width && cy < size.height {
255 + draw_cursor(scene, Point::new(cx, cy));
256 + }
257 + }
258 +}
259 +
260 +/// Klasyczna strzałka kursora (biała z ciemnym obrysem).
261 +fn draw_cursor(scene: &mut Scene, p: Point) {
262 + let arrow = [
263 + Point::new(p.x, p.y),
264 + Point::new(p.x, p.y + 17.0),
265 + Point::new(p.x + 4.2, p.y + 12.6),
266 + Point::new(p.x + 7.0, p.y + 18.5),
267 + Point::new(p.x + 9.7, p.y + 17.2),
268 + Point::new(p.x + 6.9, p.y + 11.4),
269 + Point::new(p.x + 11.5, p.y + 11.4),
270 + ];
271 + let white = Color::from_rgba8(0xf5, 0xf5, 0xf5, 0xff);
272 + let outline = Color::from_rgba8(0x10, 0x10, 0x10, 0xff);
273 + scene.fill_polygon(&arrow, white);
274 + let mut closed = arrow.to_vec();
275 + closed.push(arrow[0]);
276 + scene.stroke_polyline(&closed, 1.5, outline);
277 +}
278 +
279 +#[cfg(test)]
280 +mod tests {
281 + use super::*;
282 + use libluna_render::SoftwareRenderer;
283 +
284 + #[test]
285 + fn draw_cursor_paints_pixels() {
286 + let mut scene = Scene::new();
287 + draw_cursor(&mut scene, Point::new(8.0, 8.0));
288 + let mut renderer = SoftwareRenderer::new(48, 48);
289 + renderer.draw(&scene, 1.0, Color::from_rgba8(0, 0, 0, 255));
290 + let painted = renderer
291 + .data()
292 + .chunks_exact(4)
293 + .any(|px| px[0] > 0 || px[1] > 0 || px[2] > 0);
294 + assert!(painted, "kursor powinien zostawić niezerowe piksele");
295 + }
235 296 }
@@ -152,6 +152,9 @@ impl Window {
152 152 /// Współdzielony model okien (pisany przez wątek protokołu, czytany przez wyjście).
153 153 pub type WindowModel = Arc<Mutex<Vec<Window>>>;
154 154
155 +/// Współdzielona pozycja kursora (tryb DRM/evdev); `None` = nie rysujemy kursora.
156 +pub type CursorModel = Arc<Mutex<Option<Point>>>;
157 +
155 158 /// Trwające przeciąganie okna.
156 159 #[derive(Debug, Clone, Copy)]
157 160 pub struct DragState {
@@ -187,6 +190,8 @@ pub struct State {
187 190 pub toplevels: u32,
188 191 /// Model okien współdzielony z wątkiem wyjścia.
189 192 pub windows: WindowModel,
193 + /// Pozycja kursora do narysowania (tryb DRM/evdev), współdzielona z wątkiem wyjścia.
194 + pub cursor: CursorModel,
190 195 /// Powierzchnie okien (indeks = slot w [`State::windows`]).
191 196 pub slots: Vec<WlSurface>,
192 197 /// Licznik kolejności (z-order).
@@ -224,6 +229,7 @@ impl Default for State {
224 229 surfaces: 0,
225 230 toplevels: 0,
226 231 windows: Arc::new(Mutex::new(Vec::new())),
232 + cursor: Arc::new(Mutex::new(None)),
227 233 slots: Vec::new(),
228 234 z_counter: 1,
229 235 outputs: Arc::new(Mutex::new(vec![OutputInfo::default()])),
@@ -40,4 +40,22 @@ if mkdir -p "$log_dir" 2>/dev/null; then
40 40 exec >>"$log" 2>&1
41 41 fi
42 42
43 -exec selene
43 +# Kompozytor w tle, a po nim powłoka Luna (panel/OSD). Czekamy na gniazdo Wayland.
44 +selene &
45 +selene_pid=$!
46 +
47 +# Poczekaj, aż Selene zbindowuje gniazdo (zwykle ułamek sekundy).
48 +i=0
49 +while [ ! -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/wayland-0" ] && [ "$i" -lt 50 ]; do
50 + sleep 0.1
51 + i=$((i + 1))
52 +done
53 +
54 +# Powłoka Luna (panel, launcher, OSD) — o ile jest zainstalowana.
55 +if command -v luna-shell >/dev/null 2>&1; then
56 + export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}"
57 + luna-shell &
58 +fi
59 +
60 +# Sesja żyje, dopóki działa kompozytor.
61 +wait "$selene_pid"
@@ -40,4 +40,22 @@ if mkdir -p "$log_dir" 2>/dev/null; then
40 40 exec >>"$log" 2>&1
41 41 fi
42 42
43 -exec selene
43 +# Kompozytor w tle, a po nim powłoka Luna (panel/OSD). Czekamy na gniazdo Wayland.
44 +selene &
45 +selene_pid=$!
46 +
47 +# Poczekaj, aż Selene zbindowuje gniazdo (zwykle ułamek sekundy).
48 +i=0
49 +while [ ! -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/wayland-0" ] && [ "$i" -lt 50 ]; do
50 + sleep 0.1
51 + i=$((i + 1))
52 +done
53 +
54 +# Powłoka Luna (panel, launcher, OSD) — o ile jest zainstalowana.
55 +if command -v luna-shell >/dev/null 2>&1; then
56 + export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}"
57 + luna-shell &
58 +fi
59 +
60 +# Sesja żyje, dopóki działa kompozytor.
61 +wait "$selene_pid"