← PaganDE

Commit 652a853

4
plików
+107
dodanych
-7
usuniętych
@@ -207,6 +207,19 @@ Panel (`pagan-panel`) ma dwa przyciski obok siebie:
207 207 Ścieżkę do demo można nadpisać zmienną `PAGAN_DEMO_CMD`. Bez niej panel szuka
208 208 `pagan-demo` w `PATH`, a potem w układzie deweloperskim repozytorium.
209 209
210 +**Uruchamianie w NASZEJ sesji (ważne).** Panel nie startuje programów „jak leci”:
211 +w sesji XFCE ustawione jest `XDG_SESSION_TYPE=x11` i `DISPLAY=:0.0`, więc GUI
212 +domyślnie wybiera X11 i ląduje na pulpicie XFCE — poza naszym kompozytorem
213 +(a wtedy okno i jego dekoracja nie mają ze sobą nic wspólnego). Dlatego launcher:
214 +
215 +* wymusza `GDK_BACKEND=wayland` i `QT_QPA_PLATFORM=wayland`,
216 +* ustawia `XDG_SESSION_TYPE=wayland`,
217 +* podaje aplikacjom `DISPLAY` **naszego** XWayland, który kompozytor publikuje
218 + w `$XDG_RUNTIME_DIR/pagan/xwayland-display`.
219 +
220 +Dzięki temu aplikacje Wayland i X11 trafiają do naszego kompozytora i dostają
221 +spójną dekorację SSD.
222 +
210 223 ### Testy interakcji bez machania myszką (dev)
211 224
212 225 Na backendzie `winit` kompozytor jest zwykłym oknem X11, więc zdarzenia myszy
@@ -209,8 +209,15 @@ impl PaganWindow {
209 209 /// naszego stylu, więc gdyby rysowały własny pasek, każda wyglądałaby inaczej.
210 210 /// Negocjacja `xdg-decoration` i tak wymusza tryb serwerowy (patrz `state.rs`),
211 211 /// więc klient nie dorysuje drugiego paska.
212 + ///
213 + /// WYJĄTEK: okna X11 typu `override-redirect` (menu, podpowiedzi, popupy,
214 + /// ikony przeciągania) to elementy aplikacji, a nie samodzielne okna —
215 + /// pasek nad nimi wyglądałby jak „przesunięta dekoracja” przyklejona do menu.
212 216 pub fn wants_ssd(&self) -> bool {
213 - self.x11_surface().is_some() || self.toplevel().is_some()
217 + if let Some(x11) = self.x11_surface() {
218 + return !x11.is_override_redirect();
219 + }
220 + self.toplevel().is_some()
214 221 }
215 222
216 223 /// Dane użytkownika okna z flagami dekoracji (tworzone leniwie).
@@ -121,6 +121,7 @@ impl MyCompositor {
121 121 Ok(wm) => {
122 122 data.xwm = Some(wm);
123 123 data.xdisplay = Some(display_number);
124 + Self::publish_display(display_number);
124 125 info!(display = display_number, "XWayland gotowy (DISPLAY=:N)");
125 126 }
126 127 Err(err) => warn!(%err, "nie udało się wystartować menedżera okien X11"),
@@ -133,6 +134,43 @@ impl MyCompositor {
133 134 error!(%err, "nie udało się dodać XWayland do pętli zdarzeń");
134 135 }
135 136 }
137 +
138 + /// Publikuje numer wyświetlacza XWayland dla launcherów (panel, skrypty).
139 + ///
140 + /// PO CO: aplikacje uruchamiane przez panel dziedziczą `DISPLAY` sesji XFCE
141 + /// i lądują na pulpicie XFCE — POZA naszym kompozytorem (a wtedy nasza
142 + /// dekoracja jest gdzie indziej niż okno). Panel czyta ten plik i ustawia
143 + /// dzieciom `DISPLAY` wskazujące na NASZ XWayland.
144 + fn publish_display(display_number: u32) {
145 + let Some(path) = Self::display_file() else {
146 + return;
147 + };
148 + if let Some(dir) = path.parent() {
149 + let _ = std::fs::create_dir_all(dir);
150 + }
151 + match std::fs::write(&path, format!(":{display_number}")) {
152 + Ok(()) => info!(
153 + path = %path.display(),
154 + display = display_number,
155 + "opublikowano DISPLAY XWayland dla launcherów"
156 + ),
157 + Err(err) => {
158 + warn!(%err, path = %path.display(), "nie udało się zapisać DISPLAY XWayland")
159 + }
160 + }
161 + }
162 +
163 + /// Ścieżka pliku z numerem wyświetlacza XWayland (wspólna z panelem).
164 + ///
165 + /// PO CO w `XDG_RUNTIME_DIR`: to katalog prywatny sesji użytkownika, więc
166 + /// nie zaśmiecamy `$HOME` i nie ma ryzyka konfliktu między sesjami.
167 + pub(crate) fn display_file() -> Option<std::path::PathBuf> {
168 + std::env::var("XDG_RUNTIME_DIR").ok().map(|dir| {
169 + std::path::PathBuf::from(dir)
170 + .join("pagan")
171 + .join("xwayland-display")
172 + })
173 + }
136 174 }
137 175
138 176 impl XWaylandShellHandler for MyCompositor {
@@ -172,9 +210,16 @@ impl XwmHandler for MyCompositor {
172 210 // Ta sama polityka co dla okien Waylanda: kaskada na monitorze pod kursorem.
173 211 self.place_new_window(element.clone());
174 212
175 - // Dopasuj okno X11 do nadanej geometrii (pozycja + jego własny rozmiar).
176 - if let Some(bbox) = self.space.element_bbox(&element) {
177 - let _ = window.configure(bbox);
213 + // Dopasuj okno X11 do nadanej geometrii **TREŚCI**, a nie ramki.
214 + //
215 + // UWAGA (naprawiony błąd): `space.element_bbox()` zwraca prostokąt RAMY,
216 + // czyli z paskiem SSD (bo nasz `bbox` go obejmuje). Użycie go tutaj
217 + // przesuwało okno X11 o wysokość paska i rozciągało je w dół — aplikacja
218 + // rysowała treść w innym miejscu niż nasz pasek, co wyglądało jak
219 + // „przesunięta dekoracja". Konfigurujemy więc samą treść.
220 + if let Some(location) = self.space.element_location(&element) {
221 + let size = element.geometry().size;
222 + let _ = window.configure(Rectangle::new(location, size));
178 223 }
179 224 trace!(title = %window.title(), "X11: zmapowano okno");
180 225 }
@@ -348,7 +348,7 @@ impl Panel {
348 348 "xterm",
349 349 ] {
350 350 if which(candidate) {
351 - match Command::new(candidate).spawn() {
351 + match spawn_in_session(candidate) {
352 352 Ok(_) => {
353 353 info!(terminal = candidate, "uruchomiono terminal");
354 354 return;
@@ -370,7 +370,7 @@ impl Panel {
370 370 fn launch_demo(&self) {
371 371 if let Ok(command) = std::env::var("PAGAN_DEMO_CMD") {
372 372 if !command.trim().is_empty() {
373 - match Command::new(&command).spawn() {
373 + match spawn_in_session(&command) {
374 374 Ok(_) => {
375 375 info!(command, "uruchomiono aplikację demo (PAGAN_DEMO_CMD)");
376 376 return;
@@ -399,7 +399,7 @@ impl Panel {
399 399
400 400 for candidate in candidates {
401 401 if candidate.exists() {
402 - match Command::new(&candidate).spawn() {
402 + match spawn_in_session(&candidate.to_string_lossy()) {
403 403 Ok(_) => {
404 404 info!(path = %candidate.display(), "uruchomiono aplikację demo");
405 405 return;
@@ -513,6 +513,41 @@ fn which(name: &str) -> bool {
513 513 .unwrap_or(false)
514 514 }
515 515
516 +/// Uruchamia program w środowisku NASZEJ sesji (a nie sesji XFCE).
517 +///
518 +/// PO CO własne zmienne środowiska: w sesji XFCE ustawione jest
519 +/// `XDG_SESSION_TYPE=x11` i `DISPLAY=:0.0`, więc GUI aplikacji domyślnie wybiera
520 +/// X11 i ląduje na pulpicie XFCE — POZA naszym kompozytorem. Wtedy okno i jego
521 +/// dekoracja nie mają ze sobą nic wspólnego.
522 +///
523 +/// Dlatego:
524 +/// * wymuszamy backend Wayland dla GTK (`GDK_BACKEND`) i Qt (`QT_QPA_PLATFORM`),
525 +/// * aplikacjom X11 dajemy `DISPLAY` NASZEGO XWayland (jeśli kompozytor je
526 +/// opublikował, patrz `xwayland-display` w `XDG_RUNTIME_DIR/pagan`).
527 +fn spawn_in_session(command: &str) -> std::io::Result<std::process::Child> {
528 + let mut cmd = Command::new(command);
529 + cmd.env("XDG_SESSION_TYPE", "wayland");
530 + cmd.env("GDK_BACKEND", "wayland");
531 + cmd.env("QT_QPA_PLATFORM", "wayland");
532 + if let Some(display) = compositor_xdisplay() {
533 + cmd.env("DISPLAY", display);
534 + }
535 + cmd.spawn()
536 +}
537 +
538 +/// Czyta `DISPLAY` naszego XWayland opublikowany przez kompozytor.
539 +fn compositor_xdisplay() -> Option<String> {
540 + let dir = std::env::var("XDG_RUNTIME_DIR").ok()?;
541 + std::fs::read_to_string(
542 + std::path::PathBuf::from(dir)
543 + .join("pagan")
544 + .join("xwayland-display"),
545 + )
546 + .ok()
547 + .map(|value| value.trim().to_string())
548 + .filter(|value| !value.is_empty())
549 +}
550 +
516 551 // --- Dispatch Waylanda -------------------------------------------------------
517 552
518 553 impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for Panel {