Files
sergio 799dcef22e feat(mirada): multi-monitor real — cada salida tesela su escritorio
El Desktop deja de teselar sólo la salida primaria. Cada Output muestra
un escritorio virtual distinto y relayout() las tesela todas en un solo
Place que cubre todas las pantallas.

- Output { id, rect, workspace }; focused_output reemplaza al índice
  global active. active_index() = el escritorio de la salida enfocada.
- OutputAdded asigna el primer escritorio libre; OutputRemoved deja sus
  ventanas en su escritorio y reajusta el foco. reflow_outputs() las
  recoloca en fila.
- SwitchWorkspace actúa sobre la salida enfocada; si el escritorio
  pedido ya lo muestra otra salida, las intercambia (invariante: un
  escritorio se ve en una salida como mucho).
- DesktopAction::FocusOutputNext (Super+o) mueve el foco entre
  monitores. El foco del teclado es único — relayout() lo unifica a la
  ventana enfocada de la salida enfocada.

Verificado end-to-end con headless-ctl (ahora 2 salidas).
mirada-brain 52->58 tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 01:18:12 +00:00

115 lines
4.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Un Cerebro *headless* para probar el API de control sin gráficos.
//!
//! Abre el socket de `mirada-ctl`, arranca un [`Desktop`] con una pantalla
//! y unas ventanas de muestra, y atiende peticiones en bucle, imprimiendo
//! el estado tras cada una. Útil para ejercitar `mirada-ctl` en modo
//! desatendido.
//!
//! ```sh
//! cargo run -p mirada-brain --example headless-ctl # terminal 1
//! mirada-ctl windows # terminal 2
//! mirada-ctl focus-next
//! mirada-ctl focus-window 2
//! ```
use std::thread;
use std::time::Duration;
use mirada_brain::ctl::{self, CtlReply, CtlRequest, CtlServer};
use mirada_brain::{BodyEvent, BrainCommand, Desktop};
fn main() {
let path = ctl::default_socket_path();
let server = match CtlServer::bind(&path) {
Ok(s) => s,
Err(e) => {
eprintln!("Cerebro headless · no pude abrir el control: {e}");
std::process::exit(1);
}
};
eprintln!("Cerebro headless · control en {}", path.display());
// Dos pantallas y tres ventanas de muestra.
let mut desktop = Desktop::new();
desktop.on_event(BodyEvent::OutputAdded { id: 0, width: 1920, height: 1080 });
desktop.on_event(BodyEvent::OutputAdded { id: 1, width: 1920, height: 1080 });
for id in 1..=3 {
desktop.on_event(BodyEvent::WindowOpened {
id,
app_id: format!("org.brahman.app{id}"),
title: format!("ventana {id}"),
});
}
print_state(&desktop);
eprintln!(" esperando a mirada-ctl …");
loop {
if let Some(mut conn) = server.poll() {
if let Ok(Some(req)) = conn.read_request() {
let reply = match req {
CtlRequest::Do(action) => {
eprintln!("· {action}");
for cmd in desktop.apply(action) {
match cmd {
// La geometría que el Cerebro mandaría al Cuerpo.
BrainCommand::Place(places) => {
for p in places {
eprintln!(
" win {}{:>5}×{:<4} @ ({:>5},{:>4}){}{}",
p.id,
p.rect.w,
p.rect.h,
p.rect.x,
p.rect.y,
if p.fullscreen {
" ~pantalla"
} else if p.floating {
" ~flotante"
} else {
""
},
if p.focused { " *" } else { "" },
);
}
}
// Sin Cuerpo: simulamos nosotros el cierre.
BrainCommand::Close(id) | BrainCommand::Kill(id) => {
desktop.on_event(BodyEvent::WindowClosed { id });
}
_ => {}
}
}
print_state(&desktop);
CtlReply::Ok
}
CtlRequest::ListWindows => CtlReply::Windows(desktop.window_lines()),
};
let _ = conn.reply(&reply);
}
}
thread::sleep(Duration::from_millis(16));
}
}
fn print_state(d: &Desktop) {
let ws = d.active_workspace();
eprintln!(
" activo: escritorio {} · {:?} (maestra {:.0}%) · foco {:?}",
d.active_index() + 1,
ws.params().mode,
ws.params().master_ratio * 100.0,
d.focused_window(),
);
for (i, o) in d.outputs().iter().enumerate() {
let mark = if i == d.focused_output() { '*' } else { ' ' };
eprintln!(
" {mark} salida {} {}×{} @ x{} → escritorio {}",
o.id,
o.rect.w,
o.rect.h,
o.rect.x,
o.workspace + 1,
);
}
}