799dcef22e
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>
115 lines
4.4 KiB
Rust
115 lines
4.4 KiB
Rust
//! 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,
|
||
);
|
||
}
|
||
}
|