//! 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::::try_new()?; let display = Display::::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::() .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::(&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(()) }