Commit f05c344
9
files
+148
added
-89
deleted
@@ -7,6 +7,7 @@
7
7
//! - [`scale`] — [`Scale`] do przeliczania pikseli logicznych na fizyczne.
8
8
//! - [`event`] — [`Event`] i towarzyszące typy wejścia.
9
9
//! - [`motion`] — krzywe [`Easing`] i interpolacja [`lerp`] (matematyka ruchu).
10
+//! - [`runtime`] — wybór katalogu `XDG_RUNTIME_DIR` (wspólny dla kompozytora i klientów).
10
11
//! - [`style`] — tokeny wyglądu ([`style::Theme`], spacing, radius, typografia).
11
12
//!
12
13
//! Moduł nie zależy od Waylanda ani od konkretnego rasteryzatora — jest wspólnym
@@ -17,6 +18,7 @@ pub mod color;
17
18
pub mod event;
18
19
pub mod geometry;
19
20
pub mod motion;
21
+pub mod runtime;
20
22
pub mod scale;
21
23
pub mod style;
22
24
pub mod theme_file;
23
25
new file mode 100644
@@ -0,0 +1,102 @@
1
+//! Katalog runtime (`XDG_RUNTIME_DIR`) — wspólny dla kompozytora i klientów.
2
+//!
3
+//! Gniazdo Waylanda leży w `XDG_RUNTIME_DIR`. Menedżer sesji (logind/`pam_systemd`)
4
+//! zwykle ustawia tę zmienną, ale gdy tego nie zrobi (np. `pam_systemd` nie założył
5
+//! sesji), i kompozytor, i klienci muszą wybrać **ten sam** katalog — inaczej klient
6
+//! nie znajdzie gniazda i zgłosi `NoCompositor`. Ten moduł daje jedną, spójną logikę.
7
+
8
+use std::fs;
9
+use std::os::unix::fs::{DirBuilderExt, MetadataExt};
10
+use std::path::{Path, PathBuf};
11
+
12
+/// Bieżący uid — właściciel `/proc/self` (bez zależności od libc/rustix).
13
+fn current_uid() -> Option<u32> {
14
+ fs::metadata("/proc/self").ok().map(|m| m.uid())
15
+}
16
+
17
+/// Czy `path` to istniejący, bezwzględny katalog należący do `uid`, tryb `0700`
18
+/// (bez dostępu dla grupy i innych)?
19
+pub fn is_private_dir_owned_by(path: &Path, uid: u32) -> bool {
20
+ path.is_absolute()
21
+ && fs::metadata(path)
22
+ .map(|m| m.is_dir() && m.uid() == uid && m.mode() & 0o077 == 0)
23
+ .unwrap_or(false)
24
+}
25
+
26
+/// Zapewnia poprawny `XDG_RUNTIME_DIR`.
27
+///
28
+/// Jeśli zmienna jest już ustawiona i wskazuje prywatny katalog naszego użytkownika —
29
+/// nic nie zmieniamy. Inaczej wybieramy `/run/user/<uid>`, a gdy ono nie istnieje —
30
+/// tworzymy prywatny katalog `0700` pod `$TMPDIR`.
31
+///
32
+/// Zwraca `Some(katalog)`, gdy zmienną trzeba było ustawić (przydatne do diagnostyki),
33
+/// `None`, gdy była już poprawna albo gdy nie dało się ustalić uid.
34
+pub fn ensure_runtime_dir() -> std::io::Result<Option<PathBuf>> {
35
+ let Some(uid) = current_uid() else {
36
+ return Ok(None);
37
+ };
38
+
39
+ if let Some(dir) = std::env::var_os("XDG_RUNTIME_DIR") {
40
+ if is_private_dir_owned_by(Path::new(&dir), uid) {
41
+ return Ok(None);
42
+ }
43
+ }
44
+
45
+ let run_user = PathBuf::from(format!("/run/user/{uid}"));
46
+ let dir = if is_private_dir_owned_by(&run_user, uid) {
47
+ run_user
48
+ } else {
49
+ let base = std::env::var_os("TMPDIR")
50
+ .map(PathBuf::from)
51
+ .filter(|p| p.is_absolute())
52
+ .unwrap_or_else(|| PathBuf::from("/tmp"));
53
+ let dir = base.join(format!("luna-runtime-{uid}"));
54
+ let mut builder = fs::DirBuilder::new();
55
+ builder.mode(0o700);
56
+ match builder.create(&dir) {
57
+ Ok(()) => {}
58
+ Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
59
+ Err(e) => return Err(e),
60
+ }
61
+ if !is_private_dir_owned_by(&dir, uid) {
62
+ return Err(std::io::Error::new(
63
+ std::io::ErrorKind::PermissionDenied,
64
+ format!(
65
+ "{} nie jest prywatnym katalogiem uid {uid} (oczekiwano 0700)",
66
+ dir.display()
67
+ ),
68
+ ));
69
+ }
70
+ dir
71
+ };
72
+
73
+ std::env::set_var("XDG_RUNTIME_DIR", &dir);
74
+ Ok(Some(dir))
75
+}
76
+
77
+#[cfg(test)]
78
+mod tests {
79
+ use super::*;
80
+ use std::os::unix::fs::PermissionsExt;
81
+
82
+ #[test]
83
+ fn private_dir_of_our_uid_is_accepted() {
84
+ let uid = current_uid().expect("uid na Linuksie");
85
+ let dir = std::env::temp_dir().join(format!("luna-runtime-test-{}", std::process::id()));
86
+ let mut builder = fs::DirBuilder::new();
87
+ builder.mode(0o700);
88
+ builder.create(&dir).unwrap();
89
+
90
+ assert!(is_private_dir_owned_by(&dir, uid));
91
+ // Obcy uid.
92
+ assert!(!is_private_dir_owned_by(&dir, uid.wrapping_add(1)));
93
+ // Zbyt otwarte uprawnienia.
94
+ fs::set_permissions(&dir, fs::Permissions::from_mode(0o755)).unwrap();
95
+ assert!(!is_private_dir_owned_by(&dir, uid));
96
+ // Nieistniejąca ścieżka i ścieżka względna.
97
+ assert!(!is_private_dir_owned_by(&dir.join("missing"), uid));
98
+ assert!(!is_private_dir_owned_by(Path::new("relative/dir"), uid));
99
+
100
+ fs::remove_dir_all(&dir).ok();
101
+ }
102
+}
@@ -292,6 +292,12 @@ pub fn run<F>(config: AppConfig, render: F) -> Result<(), Box<dyn Error>>
292
292
where
293
293
F: for<'a> FnMut(&mut Frame<'a>) + 'static,
294
294
{
295
+ // Klient potrzebuje `XDG_RUNTIME_DIR`, żeby znaleźć gniazdo kompozytora. Menedżer
296
+ // sesji zwykle go ustawia; gdy brak (np. zepsuty pam_systemd), wybieramy wspólny
297
+ // katalog — ten sam co kompozytor — żeby uniknąć `NoCompositor`.
298
+ if let Ok(Some(dir)) = libluna_core::runtime::ensure_runtime_dir() {
299
+ eprintln!("libLuna: XDG_RUNTIME_DIR not set, using {}", dir.display());
300
+ }
295
301
let conn = Connection::connect_to_env()?;
296
302
let (globals, event_queue) = registry_queue_init(&conn)?;
297
303
let qh = event_queue.handle();
@@ -133,8 +133,9 @@ sudo ./scripts/install.sh --prefix /usr # instalacja syst
133
133
Pliki sesji: `de/session/luna-session` (launcher) i `de/session/selene.desktop`
134
134
(`share/wayland-sessions`).
135
135
136
-Klienta podłączysz ustawiając `WAYLAND_DISPLAY=wayland-0` i `XDG_RUNTIME_DIR` na ten sam
137
-katalog (renderowanie pojawi się, gdy wejdzie backend wyświetlania).
136
+Klienta podłączysz ustawiając `WAYLAND_DISPLAY=wayland-0` (`XDG_RUNTIME_DIR` klient wybierze
137
+awaryjnie sam, tak jak Selene — patrz `libluna_core::runtime`). Renderowanie pojawi się, gdy
138
+wejdzie backend wyświetlania.
138
139
139
140
## Bezpieczeństwo / Security
140
141
@@ -20,4 +20,4 @@ libluna-platform.workspace = true
20
20
wayland-server = "0.31"
21
21
wayland-protocols = { version = "0.32", features = ["server", "unstable"] }
22
22
wayland-protocols-wlr = { version = "0.3", features = ["server"] }
23
-rustix = { version = "0.38", features = ["event", "fs", "time", "mm", "process"] }
23
+rustix = { version = "0.38", features = ["event", "fs", "time", "mm"] }
@@ -22,9 +22,7 @@ mod sync;
22
22
23
23
use std::error::Error;
24
24
use std::os::fd::{AsRawFd, BorrowedFd};
25
-use std::os::unix::fs::MetadataExt;
26
25
use std::panic::{catch_unwind, AssertUnwindSafe};
27
-use std::path::{Path, PathBuf};
28
26
use std::sync::Arc;
29
27
30
28
use libluna_core::Event;
@@ -217,91 +215,18 @@ fn main() -> Result<(), Box<dyn Error>> {
217
215
}
218
216
}
219
217
220
-/// Zapewnia, że `XDG_RUNTIME_DIR` wskazuje prywatny katalog użytkownika, zanim
221
-/// zwiążemy gniazdo Wayland.
218
+/// Zapewnia, że `XDG_RUNTIME_DIR` jest ustawiony, zanim zwiążemy gniazdo Wayland.
222
219
///
223
-/// Menedżer sesji (logind/`pam_systemd`) normalnie ustawia tę zmienną. Gdy jej brak
224
-/// lub jest bezużyteczna (np. `pam_systemd` nie zdołał założyć sesji i zmienna nie
225
-/// trafiła do środowiska), `ListeningSocket::bind_auto` kończy się błędem
226
-/// `RuntimeDirNotSet`, a kompozytor wychodzi natychmiast — co z zewnątrz wygląda jak
227
-/// „sesja się nie uruchomiła”. Dlatego wybieramy `/run/user/<uid>`, a gdy ono nie
228
-/// istnieje — tworzymy własny katalog `0700` pod `$TMPDIR`.
220
+/// Wspólna logika (wybór `/run/user/<uid>` albo prywatnego katalogu pod `$TMPDIR`)
221
+/// mieszka w [`libluna_core::runtime`] — korzystają z niej też klienci (m.in. `luna-shell`),
222
+/// żeby trafić na to samo gniazdo. Tu tylko raportujemy wybrany katalog.
229
223
fn ensure_runtime_dir() -> Result<(), Box<dyn Error>> {
230
- let uid = rustix::process::getuid().as_raw() as u32;
231
-
232
- if let Some(dir) = std::env::var_os("XDG_RUNTIME_DIR") {
233
- if is_private_dir_owned_by(&PathBuf::from(dir), uid) {
234
- return Ok(());
235
- }
236
- }
237
-
238
- let run_user = PathBuf::from(format!("/run/user/{uid}"));
239
- let dir = if is_private_dir_owned_by(&run_user, uid) {
240
- run_user
241
- } else {
242
- let base = std::env::var_os("TMPDIR")
243
- .map(PathBuf::from)
244
- .filter(|p| p.is_absolute())
245
- .unwrap_or_else(|| PathBuf::from("/tmp"));
246
- let dir = base.join(format!("selene-runtime-{uid}"));
247
- match rustix::fs::mkdir(&dir, rustix::fs::Mode::RWXU) {
248
- Ok(()) | Err(rustix::io::Errno::EXIST) => {}
249
- Err(e) => return Err(format!("mkdir {}: {e}", dir.display()).into()),
250
- }
251
- if !is_private_dir_owned_by(&dir, uid) {
252
- return Err(format!(
253
- "runtime dir {} nie należy do uid {uid} lub jest dostępny dla grupy/innych",
254
- dir.display()
255
- )
256
- .into());
257
- }
258
- dir
259
- };
260
-
261
- println!(
262
- "{} {}",
263
- libluna_i18n::tr("XDG_RUNTIME_DIR not set, using"),
264
- dir.display()
265
- );
266
- std::env::set_var("XDG_RUNTIME_DIR", &dir);
267
- Ok(())
268
-}
269
-
270
-/// Czy `path` to istniejący, bezwzględny katalog należący do `uid`, bez dostępu dla
271
-/// grupy i innych użytkowników (tryb `0700`)?
272
-fn is_private_dir_owned_by(path: &Path, uid: u32) -> bool {
273
- path.is_absolute()
274
- && std::fs::metadata(path)
275
- .map(|m| m.is_dir() && m.uid() == uid && m.mode() & 0o077 == 0)
276
- .unwrap_or(false)
277
-}
278
-
279
-#[cfg(test)]
280
-mod runtime_dir_tests {
281
- use super::*;
282
- use std::os::unix::fs::PermissionsExt as _;
283
-
284
- #[test]
285
- fn private_dir_owned_by_uid_is_accepted() {
286
- let uid = rustix::process::getuid().as_raw() as u32;
287
- let dir = std::env::temp_dir().join(format!("selene-rdir-{}", std::process::id()));
288
- std::fs::create_dir_all(&dir).unwrap();
289
- std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)).unwrap();
290
-
291
- assert!(
292
- is_private_dir_owned_by(&dir, uid),
293
- "katalog 0700 naszego uid"
224
+ if let Some(dir) = libluna_core::runtime::ensure_runtime_dir()? {
225
+ println!(
226
+ "{} {}",
227
+ libluna_i18n::tr("XDG_RUNTIME_DIR not set, using"),
228
+ dir.display()
294
229
);
295
- // Obcy uid: nie jest nasz.
296
- assert!(!is_private_dir_owned_by(&dir, uid.wrapping_add(1)));
297
- // Zbyt otwarte uprawnienia: grupa/inny mają dostęp.
298
- std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o755)).unwrap();
299
- assert!(!is_private_dir_owned_by(&dir, uid));
300
- // Nieistniejąca ścieżka.
301
- assert!(!is_private_dir_owned_by(&dir.join("missing"), uid));
302
- // Ścieżka względna nie nadaje się na `XDG_RUNTIME_DIR`.
303
- assert!(!is_private_dir_owned_by(Path::new("relative/dir"), uid));
304
-
305
- std::fs::remove_dir_all(&dir).ok();
306
230
}
231
+ Ok(())
307
232
}
@@ -33,7 +33,7 @@ pub fn spawn(
33
33
.app_id("eu.paganlinux.Luna.Selene")
34
34
.initial_size(width, height)
35
35
.settings(ThemeSettings::default());
36
- let _ = run(config, move |frame: &mut Frame| {
36
+ let result = run(config, move |frame: &mut Frame| {
37
37
// Zgłoś bieżący rozmiar monitora (okno może być zmieniane przez rodzica).
38
38
{
39
39
let mut outs = outputs.lock_or_recover();
@@ -50,6 +50,15 @@ pub fn spawn(
50
50
// Utrzymuj klatki — model okien zmienia się poza tą pętlą.
51
51
frame.request_frame();
52
52
});
53
+ // Nested rysuje do okna na nadrzędnym kompozytorze Wayland. Gdy go nie ma
54
+ // (np. uruchomienie na X11), nie połykamy błędu — inaczej wygląda to jak
55
+ // „kompozytor działa, ale nic nie widać”.
56
+ if let Err(e) = result {
57
+ eprintln!(
58
+ "{}: {e}",
59
+ libluna_i18n::tr("nested output unavailable (no parent compositor)")
60
+ );
61
+ }
53
62
});
54
63
}
55
64
@@ -21,4 +21,11 @@ fi
21
21
: "${SELENE_BACKEND:=drm}"
22
22
export SELENE_BACKEND
23
23
24
+# Tryb DRM nie ma nadrzędnego kompozytora — wejście czytamy wprost z /dev/input
25
+# (wymaga dostępu do urządzeń: grupa `input` albo root).
26
+if [ "$SELENE_BACKEND" = "drm" ]; then
27
+ : "${SELENE_INPUT:=evdev}"
28
+ export SELENE_INPUT
29
+fi
30
+
24
31
exec selene
@@ -21,4 +21,11 @@ fi
21
21
: "${SELENE_BACKEND:=drm}"
22
22
export SELENE_BACKEND
23
23
24
+# Tryb DRM nie ma nadrzędnego kompozytora — wejście czytamy wprost z /dev/input
25
+# (wymaga dostępu do urządzeń: grupa `input` albo root).
26
+if [ "$SELENE_BACKEND" = "drm" ]; then
27
+ : "${SELENE_INPUT:=evdev}"
28
+ export SELENE_INPUT
29
+fi
30
+
24
31
exec selene