feat(mirada-compositor): backend DRM — fase 2b, bucle Wayland

Última fase del backend DRM: el bucle Wayland completo. Con esto
`mirada-compositor --drm` es un escritorio funcionando sobre una TTY,
sin sesión anfitriona.

main.rs: el armado del estado se extrae a build_app() -> Setup, que
comparten los dos backends (winit intacto).

drm_backend.rs — fase 2b sobre el pipeline de la 2a:
- DrmState: el estado que comparten los callbacks de calloop.
- bucle calloop con cinco fuentes: VBlank (DrmDeviceNotifier),
  teclado (libinput), clientes Wayland nuevos (ListeningSocket),
  peticiones de los clientes (poll fd del Display) y un timer ~60 Hz.
- render(): compone las ventanas de App en el DrmCompositor, encola el
  page-flip y reparte los frame-callbacks; el VBlank libera el flip.
- handle_input(): teclado libinput → interceptación de atajos (misma
  combo_string que winit) → keybind al Cerebro.
- tick(): Cerebro enlazado, recarga de keymap, mirada-ctl, composición.
- registra la salida con el modo del monitor; abre el socket Wayland.

Compila y pasa clippy aquí; se ejecuta y depura en hardware por logs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 02:01:29 +00:00
parent f8cb80d867
commit fe221869d2
2 changed files with 312 additions and 94 deletions
+36 -10
View File
@@ -513,9 +513,21 @@ fn load_user_rules() -> Rules {
}
}
/// El backend `winit`: corre anidado dentro de una sesión gráfica.
fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
let mut display: Display<App> = Display::new()?;
/// Lo que comparten los dos backends gráficos: el `Display` de Wayland,
/// el `App` ya armado y la maquinaria de keymap y control.
struct Setup {
display: Display<App>,
app: App,
keymap_path: Option<std::path::PathBuf>,
keymap_watch: Option<mirada_brain::KeymapWatch>,
ctl: Option<CtlServer>,
}
/// Arma el estado del compositor — todo lo independiente del backend
/// gráfico (Wayland, Cerebro, teclado, keymap, control). Cada backend
/// (winit o DRM) registra luego su propia salida y monta su bucle.
fn build_app() -> Result<Setup, Box<dyn std::error::Error>> {
let display: Display<App> = Display::new()?;
let dh = display.handle();
let mut seat_state = SeatState::new();
@@ -545,7 +557,7 @@ fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
}
};
let mut state = App {
let mut app = App {
compositor_state: CompositorState::new::<App>(&dh),
xdg_shell_state: XdgShellState::new::<App>(&dh),
shm_state: ShmState::new::<App>(&dh, Vec::new()),
@@ -562,18 +574,18 @@ fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
running: true,
};
let keyboard = state.seat.add_keyboard(Default::default(), 200, 25)?;
state.keyboard = Some(keyboard.clone());
let keyboard = app.seat.add_keyboard(Default::default(), 200, 25)?;
app.keyboard = Some(keyboard);
// En modo embebido, el propio Desktop dicta los atajos a interceptar.
if let Brain::Embedded(desktop) = &state.brain {
if let Brain::Embedded(desktop) = &app.brain {
let grab = desktop.grab_keys();
state.apply_commands(vec![grab]);
app.apply_commands(vec![grab]);
}
// Vigilancia del keymap para recargarlo en caliente — sólo tiene
// sentido con el Cerebro embebido.
let keymap_watch = match (&state.brain, &keymap_path) {
let keymap_watch = match (&app.brain, &keymap_path) {
(Brain::Embedded(_), Some(p)) => Keymap::watch(p).ok(),
_ => None,
};
@@ -583,7 +595,7 @@ fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
// API de control (mirada-ctl) — sólo con el Cerebro embebido; si es
// externo, el socket de control lo abre él.
let ctl = match &state.brain {
let ctl = match &app.brain {
Brain::Embedded(_) => {
let path = mirada_brain::ctl::default_socket_path();
match CtlServer::bind(&path) {
@@ -600,6 +612,20 @@ fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
Brain::Linked(_) => None,
};
Ok(Setup { display, app, keymap_path, keymap_watch, ctl })
}
/// El backend `winit`: corre anidado dentro de una sesión gráfica.
fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
let Setup {
mut display,
app: mut state,
keymap_path,
keymap_watch,
ctl,
} = build_app()?;
let keyboard = state.keyboard.clone().expect("teclado inicializado");
// El backend gráfico va primero. winit abre la ventana del compositor
// dentro de tu sesión gráfica anfitriona, y para encontrarla lee
// `WAYLAND_DISPLAY` / `DISPLAY` del entorno. Si publicáramos antes