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"