🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
//! Backend produkcyjny: **tty-udev** (DRM/KMS + EGL/GBM + libinput + libseat).
//!
//! PO CO: to ścieżka "prawdziwego" środowiska graficznego — kompozytor przejmuje
//! konsolę (TTY), sam ustawia tryby wyświetlania przez DRM/KMS i czyta wejście
//! bezpośrednio z libinput, z uprawnieniami zarządzanymi przez libseat (logind
//! albo seatd). W przeciwieństwie do winit nie działa "w oknie".
//!
//! Moduł jest włączany feature'em `udev` (domyślnie wyłączonym), bo wymaga
//! bibliotek systemowych: libdrm, libgbm, libinput, libudev, libseat.
//!
//! ⚠️ STATUS: kod napisany według sprawdzonych wzorców z `smithay-0.7.0/anvil`
//! (`run_udev`, `device_added`, `connector_connected`, pętla VBlank), ale NIE był
//! kompilowany ani uruchamiany na sprzęcie. Pierwsze uruchomienie wymaga TTY
//! (nie sesji graficznej) i zwykle uprawnień: użyj logind/libseat, albo uruchom
//! z innego TTY jako root. Miejsca szczególnie warte weryfikacji oznaczono
//! komentarzami `// WERYFIKUJ:`.
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::atomic::Ordering,
time::Duration,
};
use rustix::fs::OFlags;
use tracing::{error, info, warn};
use smithay::{
backend::{
allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
drm::{
compositor::FrameFlags,
exporter::gbm::GbmFramebufferExporter,
output::{DrmOutput, DrmOutputManager, DrmOutputRenderElements},
DrmDevice, DrmDeviceFd, DrmEvent, DrmNode, NodeType,
},
egl::{context::ContextPriority, EGLDevice, EGLDisplay},
input::{InputBackend, InputEvent},
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
gles::GlesRenderer,
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
ImportDma, ImportMemWl,
},
session::{libseat::LibSeatSession, Event as SessionEvent, Session},
udev::{primary_gpu, UdevBackend, UdevEvent},
},
output::{Mode as WlMode, Output, PhysicalProperties, Subpixel},
reexports::{
calloop::{EventLoop, RegistrationToken},
drm::control::{connector, crtc, ModeTypeFlags},
input::Libinput,
wayland_server::{backend::GlobalId, Display},
},
utils::{Clock, DeviceFd, Monotonic, Point, Transform},
};
// `DrmScanner` przeniesiono z Smithay do osobnego crate'a `smithay-drm-extras`.
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
use crate::{render, state::MyCompositor};
/// Renderer łączący API renderujący z API alokującym (multi-GPU).
#[allow(dead_code)]
type UdevRenderer<'a> = MultiRenderer<
'a,
'a,
GbmGlesBackend<GlesRenderer, DrmDeviceFd>,
GbmGlesBackend<GlesRenderer, DrmDeviceFd>,
>;
/// Konkretny typ kompozytora DRM/GBM (skrót dokumentacyjny).
#[allow(dead_code)]
type GbmDrmCompositor = smithay::backend::drm::compositor::DrmCompositor<
GbmAllocator<DrmDeviceFd>,
GbmFramebufferExporter<DrmDeviceFd>,
(),
DrmDeviceFd,
>;
/// Identyfikuje wyjście po (urządzenie DRM, CRTC) — wkładane do `user_data` Output.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UdevOutputId {
pub device_id: DrmNode,
pub crtc: crtc::Handle,
}
/// Stan backendu tty-udev. Trzymany w `MyCompositor::udev`.
pub struct UdevState {
/// Uchwyt sesji (libseat) — wspólny dla DRM i libinput.
pub session: LibSeatSession,
/// Nazwa seatu (np. "seat0") — do `udev_assign_seat`.
pub seat_name: String,
/// Preferowane GPU (z niego bierzemy renderer domyślny).
pub primary_gpu: DrmNode,
/// Menedżer rendererów per GPU.
pub gpus: GpuManager<GbmGlesBackend<GlesRenderer, DrmDeviceFd>>,
/// Urządzenia DRM (klucz: node), każde z własnym menedżerem wyjść.
pub backends: HashMap<DrmNode, UdevDevice>,
/// Kursor rysowany przez kompozytor (wspólny dla wszystkich wyjść).
pub pointer_element: render::PointerElement,
/// Zegar monotoniczny (m.in. do znaczników czasu klatek).
pub clock: Clock<Monotonic>,
}
/// Dane jednego urządzenia DRM.
pub struct UdevDevice {
pub drm_output_manager: DrmOutputManager<
GbmAllocator<DrmDeviceFd>,
GbmFramebufferExporter<DrmDeviceFd>,
(),
DrmDeviceFd,
>,
pub drm_scanner: DrmScanner,
pub render_node: Option<DrmNode>,
pub surfaces: HashMap<crtc::Handle, UdevSurfaceData>,
/// Uchwyt rejestracji notifiera DRM w pętli — trzymany, żeby żył.
pub registration_token: RegistrationToken,
}
/// Dane pojedynczego wyjścia (CRTC) na urządzeniu DRM.
pub struct UdevSurfaceData {
pub output: Output,
pub drm_output:
DrmOutput<GbmAllocator<DrmDeviceFd>, GbmFramebufferExporter<DrmDeviceFd>, (), DrmDeviceFd>,
pub global: Option<GlobalId>,
}
/// Punkt wejścia backendu tty-udev.
pub fn run_udev() -> anyhow::Result<()> {
let mut event_loop = EventLoop::<MyCompositor>::try_new()?;
let display = Display::<MyCompositor>::new()?;
// 1) Sesja przez libseat — daje nam uprawnienia do DRM, VT i urządzeń wejścia.
let (session, notifier) = LibSeatSession::new()
.map_err(|err| anyhow::anyhow!("nie udało się otworzyć sesji libseat: {err}"))?;
let seat_name = session.seat();
// 2) Wybór GPU. Preferujemy jawny override przez PAGAN_DRM_DEVICE.
let primary_gpu = if let Ok(var) = std::env::var("PAGAN_DRM_DEVICE") {
DrmNode::from_path(var).map_err(|err| anyhow::anyhow!("PAGAN_DRM_DEVICE: {err}"))?
} else {
primary_gpu(&seat_name)?
.and_then(|path| {
DrmNode::from_path(path)
.ok()?
.node_with_type(NodeType::Render)?
.ok()
})
.ok_or_else(|| {
anyhow::anyhow!("nie znaleziono żadnego GPU (searched seat {seat_name})")
})?
};
info!(%primary_gpu, "PaganDE: wybrano GPU");
// 3) Menedżer rendererów (openGL przez glow, kontekst o wysokim priorytecie).
let mut gpus = GpuManager::new(GbmGlesBackend::with_context_priority(ContextPriority::High))
.map_err(|err| anyhow::anyhow!("GpuManager: {err:?}"))?;
// Formaty SHM/dmabuf bierzemy z renderera ZANIM `gpus` trafi do stanu.
let (shm_formats, dmabuf_formats) = {
let renderer = gpus
.single_renderer(&primary_gpu)
.map_err(|err| anyhow::anyhow!("renderer: {err:?}"))?;
(renderer.shm_formats(), renderer.dmabuf_formats())
};
// 4) Stan protokołów kompozytora (bez wyjścia — dodamy je przy device_added).
let mut state = MyCompositor::init(display, event_loop.handle(), true);
state.shm_state.update_formats(shm_formats);
state.enable_dmabuf(dmabuf_formats);
// XWayland (aplikacje X11).
state.start_xwayland();
state.attach_udev(UdevState {
session: session.clone(),
seat_name: seat_name.clone(),
primary_gpu,
gpus,
backends: HashMap::new(),
pointer_element: render::PointerElement::default(),
clock: Clock::new(),
});
// 5) libinput — wejście. Podłączony do sesji, więc automatycznie respektuje
// pauzę/aktywację VT.
let mut libinput_context =
Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
libinput_context
.udev_assign_seat(&seat_name)
.map_err(|_| anyhow::anyhow!("libinput: nie udało się przypisać seatu {seat_name}"))?;
let libinput_backend = LibinputInputBackend::new(libinput_context.clone());
event_loop
.handle()
.insert_source(
libinput_backend,
move |event, _, data: &mut MyCompositor| {
data.process_input_event(event);
},
)
.map_err(|err| anyhow::anyhow!("rejestracja libinput: {err}"))?;
// 6) Pauza/wznowienie sesji (przełączanie VT, suspend).
{
let mut libinput_context = libinput_context.clone();
event_loop
.handle()
.insert_source(
notifier,
move |event, &mut (), data: &mut MyCompositor| match event {
SessionEvent::PauseSession => {
libinput_context.suspend();
info!("sesja wstrzymana — pauzuję wyjścia DRM");
if let Some(udev) = data.udev.as_mut() {
for device in udev.backends.values_mut() {
device.drm_output_manager.pause();
}
}
}
SessionEvent::ActivateSession => {
info!("sesja wznowiona — aktywuję wyjścia DRM");
if let Err(err) = libinput_context.resume() {
error!(?err, "libinput: nie udało się wznowić");
}
// WERYFIKUJ: `activate(false)` zachowuje stan connectorów; gdy
// sterownik tego nie lubi, zmień na `activate(true)`.
let nodes: Vec<DrmNode> = data
.udev
.as_ref()
.map(|udev| udev.backends.keys().copied().collect())
.unwrap_or_default();
for node in nodes {
if let Some(udev) = data.udev.as_mut() {
if let Some(device) = udev.backends.get_mut(&node) {
if let Err(err) = device.drm_output_manager.activate(false) {
error!(?err, %node, "nie udało się aktywować DRM");
}
}
}
data.handle
.insert_idle(move |state| state.udev_render(node));
}
}
},
)
.map_err(|err| anyhow::anyhow!("rejestracja notifiera sesji: {err}"))?;
}
// 7) Udev — wykrywanie urządzeń DRM i hotplug.
let udev_backend =
UdevBackend::new(&seat_name).map_err(|err| anyhow::anyhow!("UdevBackend: {err}"))?;
// Najpierw dodajemy urządzenia już obecne (żeby zdążyły zainicjalizować GPU).
let existing: Vec<(u64, PathBuf)> = udev_backend
.device_list()
.map(|(device_id, path)| (device_id as u64, path.to_path_buf()))
.collect();
for (device_id, path) in existing {
if let Ok(node) = DrmNode::from_dev_id(device_id as _) {
if let Err(err) = state.udev_device_added(node, &path) {
warn!(%node, ?err, "pomijam urządzenie DRM");
}
}
}
event_loop
.handle()
.insert_source(
udev_backend,
move |event, _, data: &mut MyCompositor| match event {
UdevEvent::Added { device_id, path } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
if let Err(err) = data.udev_device_added(node, &path) {
warn!(%node, ?err, "nie udało się dodać urządzenia DRM");
}
}
}
UdevEvent::Changed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
data.udev_device_changed(node);
}
}
UdevEvent::Removed { device_id } => {
if let Ok(node) = DrmNode::from_dev_id(device_id) {
data.udev_device_removed(node);
}
}
},
)
.map_err(|err| anyhow::anyhow!("rejestracja UdevBackend: {err}"))?;
info!("PaganDE (tty-udev) wystartował. Zakończ Ctrl+C lub przełącz VT.");
// 8) Pętla główna. Rendering jest sterowany zdarzeniami (VBlank → render).
while state.running.load(Ordering::SeqCst) {
if let Err(err) = event_loop.dispatch(Some(Duration::from_millis(16)), &mut state) {
error!(?err, "błąd pętli zdarzeń");
state.running.store(false, Ordering::SeqCst);
continue;
}
state.space.refresh();
// Lista okien dla paneli (tytuły, stan aktywny/zminimalizowany).
state.sync_foreign_toplevels();
state.popups.cleanup();
let _ = state.display_handle.flush_clients();
}
Ok(())
}
/// Błąd dodawania urządzenia DRM.
#[derive(Debug)]
pub enum DeviceAddError {
DeviceOpen(String),
DrmDevice(smithay::backend::drm::DrmError),
GbmDevice(std::io::Error),
AddNode(String),
}
impl std::fmt::Display for DeviceAddError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DeviceAddError::DeviceOpen(err) => write!(f, "otwarcie urządzenia: {err}"),
DeviceAddError::DrmDevice(err) => write!(f, "DrmDevice: {err:?}"),
DeviceAddError::GbmDevice(err) => write!(f, "GbmDevice: {err}"),
DeviceAddError::AddNode(err) => write!(f, "dodanie node'a GPU: {err}"),
}
}
}
impl std::error::Error for DeviceAddError {}
impl MyCompositor {
/// Inicjalizuje urządzenie DRM: otwiera je, tworzy GBM, alokator, exporter,
/// menedżer wyjść oraz rejestruje notifier VBlank.
pub fn udev_device_added(&mut self, node: DrmNode, path: &Path) -> Result<(), DeviceAddError> {
let (mut session, primary_gpu) = match self.udev.as_ref() {
Some(udev) => (udev.session.clone(), udev.primary_gpu),
None => return Err(DeviceAddError::AddNode("brak stanu udev".into())),
};
// Otwarcie urządzenia przez sesję (nadaje uprawnienia DRM master).
let fd = session
.open(
path,
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
)
.map_err(|err| DeviceAddError::DeviceOpen(format!("{err:?}")))?;
let fd = DrmDeviceFd::new(DeviceFd::from(fd));
let (drm, notifier) =
DrmDevice::new(fd.clone(), true).map_err(DeviceAddError::DrmDevice)?;
let gbm = GbmDevice::new(fd).map_err(DeviceAddError::GbmDevice)?;
// Notifier VBlank: po każdym wyświetleniu klatki planujemy kolejny render.
let registration_token = self
.handle
.insert_source(
notifier,
move |event, _metadata, data: &mut MyCompositor| match event {
DrmEvent::VBlank(crtc) => data.udev_frame_finish(node, crtc),
DrmEvent::Error(err) => error!(?err, "błąd DRM"),
},
)
.map_err(|err| DeviceAddError::AddNode(err.to_string()))?;
// Zbudowanie kontekstu EGL dla tego urządzenia i rejestracja renderera.
// WERYFIKUJ: `EGLDisplay::new` jest `unsafe` (surowe uchwyty EGL).
let display = unsafe {
EGLDisplay::new(gbm.clone()).map_err(|err| DeviceAddError::AddNode(err.to_string()))?
};
let egl_device = EGLDevice::device_for_display(&display)
.map_err(|err| DeviceAddError::AddNode(err.to_string()))?;
let render_node = egl_device
.try_get_render_node()
.ok()
.flatten()
.unwrap_or(node);
{
let udev = self
.udev
.as_mut()
.ok_or(DeviceAddError::AddNode("brak stanu udev".into()))?;
udev.gpus
.as_mut()
.add_node(render_node, gbm.clone())
.map_err(|err| DeviceAddError::AddNode(err.to_string()))?;
}
let allocator = GbmAllocator::new(
gbm.clone(),
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
);
let framebuffer_exporter = GbmFramebufferExporter::new(gbm.clone(), Some(render_node));
// Zbiór formatów renderowania wspieranych przez EGL (do alokacji buforów).
let render_formats = {
let udev = self
.udev
.as_mut()
.ok_or(DeviceAddError::AddNode("brak stanu udev".into()))?;
let mut renderer = udev
.gpus
.single_renderer(&render_node)
.map_err(|err| DeviceAddError::AddNode(format!("{err:?}")))?;
renderer
.as_mut()
.egl_context()
.dmabuf_render_formats()
.iter()
.copied()
.collect::<Vec<_>>()
};
let drm_output_manager = DrmOutputManager::new(
drm,
allocator,
framebuffer_exporter,
Some(gbm),
[
smithay::backend::allocator::Fourcc::Argb8888,
smithay::backend::allocator::Fourcc::Xrgb8888,
],
render_formats,
);
let udev = self
.udev
.as_mut()
.ok_or(DeviceAddError::AddNode("brak stanu udev".into()))?;
udev.backends.insert(
node,
UdevDevice {
drm_output_manager,
drm_scanner: DrmScanner::new(),
render_node: Some(render_node),
surfaces: HashMap::new(),
registration_token,
},
);
let _ = primary_gpu;
// Skanujemy już podłączone monitory.
self.udev_device_changed(node);
Ok(())
}
/// Reakcja na zmianę urządzenia: skanuje konektory i dodaje/usuwa wyjścia.
pub fn udev_device_changed(&mut self, node: DrmNode) {
let scan_result = {
let Some(udev) = self.udev.as_mut() else {
return;
};
let Some(device) = udev.backends.get_mut(&node) else {
return;
};
match device
.drm_scanner
.scan_connectors(device.drm_output_manager.device())
{
Ok(result) => result,
Err(err) => {
warn!(?err, %node, "skanowanie konektorów nie powiodło się");
return;
}
}
};
for event in scan_result {
match event {
DrmScanEvent::Connected {
connector,
crtc: Some(crtc),
} => {
self.udev_connector_connected(node, connector, crtc);
}
DrmScanEvent::Disconnected {
connector: _,
crtc: Some(crtc),
} => {
self.udev_connector_disconnected(node, crtc);
}
_ => {}
}
}
}
/// Tworzy wyjście Waylanda i kompozytor DRM dla podłączonego konektora.
pub fn udev_connector_connected(
&mut self,
node: DrmNode,
connector: connector::Info,
crtc: crtc::Handle,
) {
let output_name = format!(
"{}-{}",
connector.interface().as_str(),
connector.interface_id()
);
// Wybierz tryb preferowany (albo pierwszy dostępny).
let mode_id = connector
.modes()
.iter()
.position(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
.unwrap_or(0);
let Some(drm_mode) = connector.modes().get(mode_id).copied() else {
warn!(%output_name, "konektor bez trybów");
return;
};
let wl_mode = WlMode::from(drm_mode);
// Układ: nowe wyjście na prawo od już istniejących.
let x = self.space.outputs().fold(0, |acc, output| {
acc + self
.space
.output_geometry(output)
.map(|g| g.size.w)
.unwrap_or(0)
});
let position = Point::from((x, 0));
let output = Output::new(
output_name.clone(),
PhysicalProperties {
// Fizyczny rozmiar panelu (mm) — informacyjny dla klientów.
size: connector
.size()
.map(|(w, h)| (w as i32, h as i32))
.unwrap_or((0, 0))
.into(),
subpixel: Subpixel::Unknown,
make: "PaganDE".into(),
model: "DRM".into(),
},
);
let global = output.create_global::<MyCompositor>(&self.display_handle);
output.set_preferred(wl_mode);
output.change_current_state(Some(wl_mode), Some(Transform::Normal), None, Some(position));
self.space.map_output(&output, position);
output.user_data().insert_if_missing(|| UdevOutputId {
device_id: node,
crtc,
});
// Ustawiamy wszystkie wyjścia poziomo (bez przerw) i ratujemy okna,
// jeśli nowy monitor zmienił układ.
self.relayout_outputs();
// Renderer dla tego GPU.
let Some(udev) = self.udev.as_mut() else {
return;
};
let render_node = udev
.backends
.get(&node)
.and_then(|device| device.render_node)
.unwrap_or(udev.primary_gpu);
let mut renderer = match udev.gpus.single_renderer(&render_node) {
Ok(renderer) => renderer,
Err(err) => {
warn!(?err, "brak renderera dla wyjścia");
return;
}
};
let Some(device) = udev.backends.get_mut(&node) else {
return;
};
let planes = device.drm_output_manager.device().planes(&crtc).ok();
let drm_output = match device
.drm_output_manager
.initialize_output::<GlesRenderer, render::OutputRenderElements>(
crtc,
drm_mode,
&[connector.handle()],
&output,
planes,
renderer.as_mut(),
&DrmOutputRenderElements::default(),
) {
Ok(drm_output) => drm_output,
Err(err) => {
warn!(?err, %output_name, "nie udało się zainicjalizować wyjścia DRM");
return;
}
};
device.surfaces.insert(
crtc,
UdevSurfaceData {
output: output.clone(),
drm_output,
global: Some(global),
},
);
info!(%output_name, ?crtc, "skonfigurowano wyjście");
// Pierwsza klatka.
self.handle
.insert_idle(move |state| state.udev_render(node));
}
/// Usuwa wyjście dla odłączonego konektora.
pub fn udev_connector_disconnected(&mut self, node: DrmNode, crtc: crtc::Handle) {
// Zdejmujemy pożyczkę na `self.udev` w bloku, żeby potem móc wywołać
// `relayout_outputs` (metoda przyjmująca całe `&mut self`).
let removed = {
let Some(udev) = self.udev.as_mut() else {
return;
};
let Some(device) = udev.backends.get_mut(&node) else {
return;
};
device.surfaces.remove(&crtc)
};
let Some(surface) = removed else { return };
if let Some(global) = surface.global.as_ref() {
self.display_handle
.remove_global::<MyCompositor>(global.clone());
}
self.space.unmap_output(&surface.output);
info!(?crtc, "usunięto wyjście");
// Re-layout bez przerwy i przeniesienie okien z usuniętego ekranu.
self.relayout_outputs();
}
/// Usuwa całe urządzenie DRM (odłączone/utracone).
pub fn udev_device_removed(&mut self, node: DrmNode) {
let removed = {
let Some(udev) = self.udev.as_mut() else {
return;
};
udev.backends.remove(&node)
};
let Some(device) = removed else { return };
for surface in device.surfaces.values() {
if let Some(global) = surface.global.as_ref() {
self.display_handle
.remove_global::<MyCompositor>(global.clone());
}
self.space.unmap_output(&surface.output);
}
info!(%node, "usunięto urządzenie DRM");
self.relayout_outputs();
}
/// Renderuje i kolejkuje klatkę na wszystkich wyjściach danego urządzenia.
pub fn udev_render(&mut self, node: DrmNode) {
let crtcs: Vec<crtc::Handle> = self
.udev
.as_ref()
.and_then(|udev| udev.backends.get(&node))
.map(|device| device.surfaces.keys().copied().collect())
.unwrap_or_default();
for crtc in crtcs {
self.udev_render_surface(node, crtc);
}
}
/// Renderuje jedną klatkę wyjścia i — jeśli coś się zmieniło — kolejkuje ją.
pub fn udev_render_surface(&mut self, node: DrmNode, crtc: crtc::Handle) {
let Some(output) = self.udev_output(node, crtc) else {
return;
};
let render_node = {
let Some(udev) = self.udev.as_ref() else {
return;
};
udev.backends
.get(&node)
.and_then(|device| device.render_node)
.unwrap_or(udev.primary_gpu)
};
let pointer_location = self.pointer_location;
let cursor_status = self.cursor_status.clone();
let Some(udev) = self.udev.as_mut() else {
return;
};
// Kursor WYJMUJEMY na czas renderu: `render_elements` potrzebuje `&mut`,
// a jednocześnie pożyczamy `udev.gpus` i `udev.backends` — wyjęcie elementu
// rozplątuje te pożyczki bez walki z borrow checkerem.
let mut pointer_element = std::mem::take(&mut udev.pointer_element);
pointer_element.update(render::pointer_physical(pointer_location), true);
pointer_element.set_status(cursor_status);
let mut renderer = match udev.gpus.single_renderer(&render_node) {
Ok(renderer) => renderer,
Err(err) => {
warn!(?err, "brak renderera podczas renderowania");
udev.pointer_element = pointer_element;
return;
}
};
let Some(device) = udev.backends.get_mut(&node) else {
udev.pointer_element = pointer_element;
return;
};
let Some(surface) = device.surfaces.get_mut(&crtc) else {
udev.pointer_element = pointer_element;
return;
};
// Nasz renderer jest zbudowany na GlesRenderer (pixel shadery paska SSD), a
// `MultiRenderer` udostępnia wewnętrzny renderer przez `AsMut`.
let gles: &mut GlesRenderer = renderer.as_mut();
let (elements, clear_color) = render::output_elements(
&self.space,
&output,
Some(&mut pointer_element),
self.dnd_icon.as_ref(),
self.snap_preview,
pointer_location,
gles,
);
match surface
.drm_output
.render_frame(gles, &elements, clear_color, FrameFlags::DEFAULT)
{
Ok(result) => {
if !result.is_empty {
if let Err(err) = surface.drm_output.queue_frame(()) {
warn!(?err, "nie udało się zakolejkować klatki");
}
}
}
Err(err) => warn!(?err, "błąd renderowania wyjścia"),
}
// Oddajemy kursor z powrotem do stanu backendu.
udev.pointer_element = pointer_element;
}
/// Po VBlank: kończymy klatkę i planujemy kolejny render.
pub fn udev_frame_finish(&mut self, node: DrmNode, crtc: crtc::Handle) {
let Some(udev) = self.udev.as_mut() else {
return;
};
if let Some(device) = udev.backends.get_mut(&node) {
if let Some(surface) = device.surfaces.get_mut(&crtc) {
// Przetwarza zakończoną klatkę (zwalnia bufory, domyka kolejki).
let _ = surface.drm_output.frame_submitted();
}
}
// Zakolejkowane przechwycenia ekranu (wlr-screencopy) — mamy tu renderer.
if !self.screencopy_queue.is_empty() {
let render_node = self
.udev
.as_ref()
.map(|udev| udev.primary_gpu)
.unwrap_or(node);
if let Some(udev) = self.udev.as_mut() {
if let Ok(mut renderer) = udev.gpus.single_renderer(&render_node) {
crate::screencopy::process(
renderer.as_mut(),
&self.space,
self.pointer_location,
&mut self.screencopy_queue,
);
}
}
}
// Zaplanuj kolejną klatkę. Prosty, ciągły rendering na VBlank wystarcza
// na start; optymalizacja to renderowanie tylko przy uszkodzeniach.
self.handle
.insert_idle(move |state| state.udev_render(node));
}
/// Znajduje `Output` odpowiadające parze (urządzenie, CRTC).
fn udev_output(&self, node: DrmNode, crtc: crtc::Handle) -> Option<Output> {
self.space
.outputs()
.find(|output| {
output.user_data().get::<UdevOutputId>()
== Some(&UdevOutputId {
device_id: node,
crtc,
})
})
.cloned()
}
/// Przetwarza zdarzenie wejścia z libinput (backend udev).
///
/// PO CO osobno od `process_input_event_windowed`: libinput podaje ruch
/// WZGLĘDNY (delty), a winit absolutny. Obsługa przycisków/scrolla/klawiatury
/// jest wspólna (`input.rs`).
pub fn process_input_event<B: InputBackend>(&mut self, event: InputEvent<B>) {
match event {
InputEvent::Keyboard { event } => self.handle_keyboard::<B>(event),
InputEvent::PointerMotion { event } => self.on_pointer_move::<B>(event),
InputEvent::PointerButton { event } => self.on_pointer_button::<B>(event),
InputEvent::PointerAxis { event } => self.on_pointer_axis::<B>(event),
_ => {}
}
}
}