🔒 Repository is read-only – file editing is disabled.

PaganDE/pagan-compositor/src/winit.rs main

209 linii Raw ← Powrót
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(())
}