🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
//! Backend okienkowy (winit) — tryb DEVELOPMENT.
//!
//! PO CO: pozwala uruchomić i testować kompozytor jako zwykłe okno w X11 lub
//! na innym Waylandzie, bez roota i bez przejmowania całego TTY. To najszybsza
//! pętla developerska: `cargo run -- --winit`.
//!
//! Docelowo (produkcja) dojdzie backend tty-udev (DRM/KMS + libinput +
//! libseat). `MyCompositor::init` jest już od backendu odseparowany, więc
//! podłączenie udev nie wymaga zmian w logice okien.
use std::{sync::atomic::Ordering, time::Duration};
use smithay::{
backend::{
renderer::{damage::OutputDamageTracker, gles::GlesRenderer, ImportDma, ImportMemWl},
winit::{self, WinitEvent},
},
output::{Mode, Output, PhysicalProperties, Subpixel},
reexports::{
calloop::EventLoop, wayland_server::Display, winit::platform::pump_events::PumpStatus,
},
utils::Transform,
};
use tracing::{error, info, warn};
use crate::{render, state::MyCompositor};
/// Nazwa wyjścia widoczna dla klientów.
pub const OUTPUT_NAME: &str = "pagan-winit";
/// Uruchamia kompozytor w trybie okienkowym.
pub fn run_winit() -> anyhow::Result<()> {
// Pętla zdarzeń calloop + serwer Waylanda.
let mut event_loop = EventLoop::<MyCompositor>::try_new()?;
let display = Display::<MyCompositor>::new()?;
let mut display_handle = display.handle();
// Inicjalizacja backendu winit z rendererem OpenGL (Smithay `GlesRenderer`
// jest zbudowany na `glow`). Zwraca backend graficzny i pętlę winit.
let (mut backend, mut winit) = winit::init::<GlesRenderer>()
.map_err(|err| anyhow::anyhow!("nie udało się zainicjować winit: {err}"))?;
// Wyjście odpowiada rozmiarowi okna winit.
let size = backend.window_size();
let mode = Mode {
size,
refresh: 60_000,
};
let output = Output::new(
OUTPUT_NAME.to_string(),
PhysicalProperties {
size: (0, 0).into(),
subpixel: Subpixel::Unknown,
make: "PaganDE".into(),
model: "Winit".into(),
},
);
let _global = output.create_global::<MyCompositor>(&display.handle());
// Uwaga: `Flipped180` jest zgodne z tym, jak anvil konfiguruje wyjście pod
// winit — GL oddaje framebuffer "do góry nogami", a transform to koryguje.
output.change_current_state(
Some(mode),
Some(Transform::Flipped180),
None,
Some((0, 0).into()),
);
output.set_preferred(mode);
// Tracker uszkodzeń: liczy, co trzeba przerysować na kolejnej klatce.
let mut damage_tracker = OutputDamageTracker::from_output(&output);
// Stan kompozytora (rejestruje socket, globals, seat).
let mut state = MyCompositor::init(display, event_loop.handle(), true);
state.space.map_output(&output, (0, 0));
// Formaty pamięci współdzielonej i buforów GPU bierzemy z renderera.
// Bez `update_formats` klienci SHM nie mieliby zadeklarowanego żadnego formatu.
state
.shm_state
.update_formats(backend.renderer().shm_formats());
state.enable_dmabuf(backend.renderer().dmabuf_formats());
// Skompiluj shadery własne już teraz — błąd GLSL ma być widoczny w logu
// startowym, a nie dopiero przy pierwszym przeciąganiu okna.
render::warm_up_shaders(backend.renderer());
// XWayland (aplikacje X11). Wołane po `init`, bo wstawia źródło do pętli.
state.start_xwayland();
if let Some(name) = state.socket_name.clone() {
info!(socket = %name, "PaganDE gotowy — ustaw WAYLAND_DISPLAY, aby podłączyć klienta");
// Wypisujemy na stdout, żeby łatwo podstawić do uruchomienia demo toolkitu.
println!("WAYLAND_DISPLAY={name}");
}
let mut pointer_element = render::PointerElement::default();
info!("Startuję pętlę główną (backend winit).");
while state.running.load(Ordering::SeqCst) {
// 1) Zdarzenia winit (resize, mysz, klawiatura).
let status = winit.dispatch_new_events(|event| match event {
WinitEvent::Resized { size, .. } => {
// Mamy jedno wyjście — aktualizujemy jego tryb (rozmiar "monitora").
// Szukamy PRZED `&mut state`: iterator po `state.space` trzymałby
// pożyczkę niemutowalną do końca `if let`.
let output = state.space.outputs().next().cloned();
if let Some(output) = output {
let mode = Mode {
size,
refresh: 60_000,
};
output.change_current_state(Some(mode), None, None, None);
output.set_preferred(mode);
state.space.map_output(&output, (0, 0));
}
// Po zmianie rozmiaru ekranu okna mogły wyjść za krawędź — ratujemy je.
state.relayout_outputs();
}
WinitEvent::Input(event) => state.process_input_event_windowed(event, OUTPUT_NAME),
_ => {}
});
if let PumpStatus::Exit(_) = status {
state.running.store(false, Ordering::SeqCst);
break;
}
// 2) Klatka renderowania.
{
let pointer_physical = render::pointer_physical(state.pointer_location);
pointer_element.update(pointer_physical, true);
// Kształt kursora pochodzi z `SeatHandler::cursor_image` (klient albo my).
pointer_element.set_status(state.cursor_status.clone());
let age = backend.buffer_age().unwrap_or(0);
let space = &state.space;
// Ikona DnD, duch snapu i pozycja kursora są tylko czytane w tym bloku.
let dnd = state.dnd_icon.as_ref();
let preview = state.snap_preview;
let pointer_location = state.pointer_location;
let render_res = backend.bind().and_then(|(renderer, mut framebuffer)| {
render::render_output(
&output,
space,
Some(&mut pointer_element),
dnd,
preview,
pointer_location,
renderer,
&mut framebuffer,
&mut damage_tracker,
age,
)
.map_err(|err| match err {
smithay::backend::renderer::damage::Error::Rendering(err) => err.into(),
_ => unreachable!("render_output zwraca tylko błąd renderowania"),
})
});
match render_res {
Ok(result) => {
if let Some(damage) = result.damage {
if let Err(err) = backend.submit(Some(damage)) {
warn!("Nie udało się oddać bufora: {}", err);
}
}
}
Err(err) => {
error!("Krytyczny błąd renderowania: {}", err);
state.running.store(false, Ordering::SeqCst);
}
}
}
// 3) Dispatch klientów Waylanda.
if event_loop
.dispatch(Some(Duration::from_millis(1)), &mut state)
.is_err()
{
state.running.store(false, Ordering::SeqCst);
} else {
// Zakolejkowane przechwycenia ekranu (wlr-screencopy) realizujemy
// tutaj, gdzie mamy renderer — zanim wyślemy klientom `ready`.
if !state.screencopy_queue.is_empty() {
if let Ok((renderer, _framebuffer)) = backend.bind() {
crate::screencopy::process(
renderer,
&state.space,
state.pointer_location,
&mut state.screencopy_queue,
);
}
}
state.space.refresh();
// Lista okien dla paneli (tytuły, stan aktywny/zminimalizowany).
state.sync_foreign_toplevels();
state.popups.cleanup();
display_handle
.flush_clients()
.expect("flush_clients nie powiodło się");
}
}
Ok(())
}