← luna

Commit 90ad3d5

5
files
+75
added
-12
deleted
@@ -111,6 +111,10 @@ założył sesji), Selene wybiera katalog sam: najpierw `/run/user/<uid>`, a gdy
111 111 istnieje — tworzy prywatny katalog `0700` pod `$TMPDIR`. Skrypt `luna-session` robi ten
112 112 sam wybór wcześniej, żeby zmienną dziedziczyli także klienci.
113 113
114 +Log uruchomienia sesji trafia do stałego pliku `~/.local/state/luna/session.log`
115 +(LightDM trzyma tylko dwa pliki `.xsession-errors` i obraca je przy każdej sesji, więc
116 +bez tego diagnostyka przepada).
117 +
114 118 Tryb nested (domyślny) uruchamia okno na nadrzędnym kompozytorze. **Realne wyjście DRM**
115 119 (gdy masz wolnego mastera, np. z osobnej konsoli):
116 120
@@ -446,6 +446,17 @@ struct DrmOutput {
446 446 front: usize,
447 447 }
448 448
449 +/// Powód, dla którego nie udało się otworzyć wyjścia DRM (do diagnostyki).
450 +#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451 +pub enum DrmError {
452 + /// Inna sesja trzyma mastera DRM — nie odbieramy ekranu.
453 + MasterBusy,
454 + /// Nie udało się odczytać zasobów karty (`GETRESOURCES`).
455 + Resources,
456 + /// Nie udało się ustawić trybu na żadnym podłączonym monitorze.
457 + NoOutputs,
458 +}
459 +
449 460 /// Backend wyjścia DRM/KMS (jedna karta, wiele monitorów).
450 461 pub struct DrmBackend {
451 462 fd: OwnedFd,
@@ -462,17 +473,20 @@ impl Drop for DrmBackend {
462 473 impl DrmBackend {
463 474 /// Otwiera kartę, przejmuje mastera i ustawia tryb na wszystkich podłączonych monitorach.
464 475 /// Zwraca `None`, gdy DRM jest niedostępne lub ktoś inny trzyma mastera (np. sesja użytkownika).
465 - pub fn open() -> Option<Self> {
476 + pub fn open() -> Result<Self, DrmError> {
477 + let mut last = DrmError::NoOutputs;
466 478 for card in card_paths() {
467 479 let Ok(fd) = rustix::fs::open(&card, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty())
468 480 else {
469 481 continue;
470 482 };
471 483 if ioctl_none::<DRM_IOCTL_SET_MASTER, _>(&fd).is_err() {
472 - continue; // inny master (uruchomiona sesja) — nie odbieramy ekranu
484 + last = DrmError::MasterBusy; // inna sesja trzyma mastera
485 + continue;
473 486 }
474 487 let Some(res) = fetch_resources(fd.as_fd()) else {
475 488 let _ = ioctl_none::<DRM_IOCTL_DROP_MASTER, _>(&fd);
489 + last = DrmError::Resources;
476 490 continue;
477 491 };
478 492 let mut outputs = Vec::new();
@@ -483,11 +497,12 @@ impl DrmBackend {
483 497 }
484 498 if outputs.is_empty() {
485 499 let _ = ioctl_none::<DRM_IOCTL_DROP_MASTER, _>(&fd);
500 + last = DrmError::NoOutputs;
486 501 continue;
487 502 }
488 - return Some(Self { fd, outputs });
503 + return Ok(Self { fd, outputs });
489 504 }
490 - None
505 + Err(last)
491 506 }
492 507
493 508 /// `(nazwa, szerokość, wysokość)` każdego wyjścia.
@@ -12,7 +12,7 @@ use libluna_core::{Color, Point, Rect, Size, ThemeSettings};
12 12 use libluna_platform::{run, AppConfig, Frame};
13 13 use libluna_render::{Scene, SoftwareRenderer, TextRun};
14 14
15 -use crate::drm::DrmBackend;
15 +use crate::drm::{DrmBackend, DrmError};
16 16 use crate::input::{self, Events};
17 17 use crate::monitors::Outputs;
18 18 use crate::state::{Window, WindowModel};
@@ -68,12 +68,24 @@ pub fn spawn(
68 68 /// (wywołujący powinien wtedy użyć wyjścia nested).
69 69 pub fn spawn_drm(model: WindowModel) {
70 70 std::thread::spawn(move || {
71 - let Some(mut backend) = DrmBackend::open() else {
72 - eprintln!(
73 - "{}",
74 - libluna_i18n::tr("DRM backend unavailable (no master)")
75 - );
76 - return;
71 + let mut backend = match DrmBackend::open() {
72 + Ok(backend) => backend,
73 + Err(reason) => {
74 + // Rozróżniamy powód — inaczej „brak mastera” maskuje błędy inicjalizacji.
75 + let msg = match reason {
76 + DrmError::MasterBusy => {
77 + libluna_i18n::tr("DRM backend unavailable (master held by another session)")
78 + }
79 + DrmError::Resources => {
80 + libluna_i18n::tr("DRM backend unavailable (cannot read card resources)")
81 + }
82 + DrmError::NoOutputs => {
83 + libluna_i18n::tr("DRM backend unavailable (no monitor could be set up)")
84 + }
85 + };
86 + eprintln!("{msg}");
87 + return;
88 + }
77 89 };
78 90 let infos = backend.outputs();
79 91 let sizes: Vec<(u32, u32)> = infos.iter().map(|(_, w, h)| (*w, *h)).collect();
@@ -98,6 +110,8 @@ pub fn spawn_drm(model: WindowModel) {
98 110 .collect();
99 111 let bg = Color::from_rgba8(0x1e, 0x20, 0x28, 0xff);
100 112
113 + // Gdy page-flip zawodzi (np. sterownik/karta), ekran zostaje czarny — logujemy raz.
114 + let mut flip_error_logged = false;
101 115 loop {
102 116 for i in 0..sizes.len() {
103 117 let (w, h) = sizes[i];
@@ -105,7 +119,13 @@ pub fn spawn_drm(model: WindowModel) {
105 119 let mut scene = Scene::new();
106 120 build_scene(&mut scene, Size::new(w as f64, h as f64), ox, oy, &model);
107 121 renderers[i].draw(&scene, 1.0, bg);
108 - backend.present(i, renderers[i].data());
122 + if !backend.present(i, renderers[i].data()) && !flip_error_logged {
123 + eprintln!(
124 + "{}",
125 + libluna_i18n::tr("DRM page-flip failed (screen may stay blank)")
126 + );
127 + flip_error_logged = true;
128 + }
109 129 backend.wait_flip();
110 130 }
111 131 // Bez wejścia w trybie DRM odświeżamy w stałym rytmie (~60 Hz).
@@ -28,4 +28,16 @@ if [ "$SELENE_BACKEND" = "drm" ]; then
28 28 export SELENE_INPUT
29 29 fi
30 30
31 +# Trwały log sesji: LightDM trzyma tylko 2 pliki `.xsession-errors` i obraca je przy
32 +# każdym starcie sesji, przez co logi Luny przepadają. Piszemy do stałego pliku w
33 +# katalogu stanu użytkownika, żeby każdą próbę dało się obejrzeć po fakcie.
34 +log_dir="${XDG_STATE_HOME:-$HOME/.local/state}/luna"
35 +if mkdir -p "$log_dir" 2>/dev/null; then
36 + log="$log_dir/session.log"
37 + printf '=== luna-session %s ===\nXDG_RUNTIME_DIR=%s SELENE_BACKEND=%s SELENE_INPUT=%s\n' \
38 + "$(date -Is)" "${XDG_RUNTIME_DIR:-<brak>}" "$SELENE_BACKEND" "${SELENE_INPUT:-<brak>}" \
39 + >>"$log" 2>/dev/null || true
40 + exec >>"$log" 2>&1
41 +fi
42 +
31 43 exec selene
@@ -28,4 +28,16 @@ if [ "$SELENE_BACKEND" = "drm" ]; then
28 28 export SELENE_INPUT
29 29 fi
30 30
31 +# Trwały log sesji: LightDM trzyma tylko 2 pliki `.xsession-errors` i obraca je przy
32 +# każdym starcie sesji, przez co logi Luny przepadają. Piszemy do stałego pliku w
33 +# katalogu stanu użytkownika, żeby każdą próbę dało się obejrzeć po fakcie.
34 +log_dir="${XDG_STATE_HOME:-$HOME/.local/state}/luna"
35 +if mkdir -p "$log_dir" 2>/dev/null; then
36 + log="$log_dir/session.log"
37 + printf '=== luna-session %s ===\nXDG_RUNTIME_DIR=%s SELENE_BACKEND=%s SELENE_INPUT=%s\n' \
38 + "$(date -Is)" "${XDG_RUNTIME_DIR:-<brak>}" "$SELENE_BACKEND" "${SELENE_INPUT:-<brak>}" \
39 + >>"$log" 2>/dev/null || true
40 + exec >>"$log" 2>&1
41 +fi
42 +
31 43 exec selene