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 {