be61ddb6eb
ToggleFullscreen (Super+Shift+f) lleva la ventana enfocada a pantalla completa: cubre toda la salida sin gap, oculta al resto y se lleva el foco. Distinto del modo Monocle (un modo de teselado): es un estado por ventana que ignora el layout. - Workspace.fullscreen: Option<WindowId>; set_fullscreen / fullscreen(); remove() lo limpia si se cierra esa ventana. - placements() da a la fullscreen el rect completo y marca al resto visible: false. WindowPlacement y BodyOp::Configure llevan fullscreen: bool. - mirada-compositor fija el estado xdg_toplevel::Fullscreen en la superficie, para que el cliente lo sepa. - Cableado en keymap, HUD de mirada y mirada-ctl. Verificado end-to-end con headless-ctl. mirada-protocol 10->11, mirada-brain 51->52. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
103 lines
4.0 KiB
Rust
103 lines
4.0 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());
|
||
|
||
// Una pantalla y tres ventanas de muestra.
|
||
let mut desktop = Desktop::new();
|
||
desktop.on_event(BodyEvent::OutputAdded { id: 0, 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!(
|
||
" escritorio {} · {:?} (maestra {:.0}%) · foco {:?}",
|
||
d.active_index() + 1,
|
||
ws.params().mode,
|
||
ws.params().master_ratio * 100.0,
|
||
d.focused_window(),
|
||
);
|
||
}
|