feat(mirada): API de acciones — mirada-ctl + HUD interactivo
Toda acción de escritorio converge en Desktop::apply(DesktopAction); el keymap era sólo un front-end. Esta tanda añade los otros tres. - DesktopAction::FocusWindow(WindowId): direccionamiento directo de una ventana (no sólo ciclar); si está en otro escritorio, salta a él. DesktopAction pasa a ser Serialize/Deserialize (postcard) además de Display/FromStr. - mirada-brain::ctl: el API de control externo. CtlRequest/CtlReply (marco postcard), CtlServer/CtlConn no bloqueantes y send_request. El Cerebro abre el socket y atiende en su bucle: la app mirada siempre, mirada-compositor sólo con el Cerebro embebido. - mirada-ctl: CLI de control estilo swaymsg/hyprctl — `mirada-ctl focus-next | focus-window 5 | workspace 3 | windows`. Parsea la acción de los argumentos vía FromStr. - HUD interactivo en la app mirada: pips de escritorio y ventanas del lienzo clicables (SwitchWorkspace / FocusWindow). - Ejemplo headless-ctl: un Cerebro sin gráficos para probar mirada-ctl en modo desatendido. Verificado end-to-end. mirada-brain: 29 -> 37 tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,21 @@ El compositor en sí no interpreta atajos: sólo intercepta las
|
||||
combinaciones que el Cerebro le pide (`GrabKeys`) y le devuelve la
|
||||
pulsada. *Qué significa* cada una lo decide `mirada-brain`. Ver el SDD.
|
||||
|
||||
## Control externo
|
||||
|
||||
En modo autónomo, el compositor abre un socket de control y `mirada-ctl`
|
||||
lo maneja desde la terminal — al estilo de `swaymsg`/`hyprctl`:
|
||||
|
||||
```sh
|
||||
mirada-ctl focus-next # cambia el foco
|
||||
mirada-ctl focus-window 5 # enfoca una ventana concreta
|
||||
mirada-ctl workspace 3 # va al escritorio 3
|
||||
mirada-ctl windows # lista las ventanas
|
||||
```
|
||||
|
||||
En modo enlazado el socket de control lo abre el Cerebro (la app
|
||||
`mirada`), no el compositor.
|
||||
|
||||
## Qué implementa
|
||||
|
||||
`wl_compositor`, `xdg_shell` (toplevels y popups), `wl_shm`, `wl_seat`
|
||||
|
||||
@@ -59,7 +59,7 @@ use smithay::{
|
||||
};
|
||||
|
||||
use mirada_body::{BodyOp, BodyState};
|
||||
use mirada_brain::{BodyEvent, BrainCommand, Desktop, Keymap};
|
||||
use mirada_brain::{BodyEvent, BrainCommand, CtlReply, CtlRequest, CtlServer, Desktop, Keymap};
|
||||
use mirada_link::BodyLink;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -132,6 +132,31 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
/// Atiende una petición del API de control (`mirada-ctl`).
|
||||
fn serve_ctl(&mut self, req: CtlRequest) -> CtlReply {
|
||||
match req {
|
||||
CtlRequest::Do(action) => {
|
||||
let cmds = match &mut self.brain {
|
||||
Brain::Embedded(d) => Some(d.apply(action)),
|
||||
Brain::Linked(_) => None,
|
||||
};
|
||||
match cmds {
|
||||
Some(cmds) => {
|
||||
self.apply_commands(cmds);
|
||||
CtlReply::Ok
|
||||
}
|
||||
None => CtlReply::Error(
|
||||
"el Cerebro es externo; usa mirada-ctl contra la app mirada".into(),
|
||||
),
|
||||
}
|
||||
}
|
||||
CtlRequest::ListWindows => match &self.brain {
|
||||
Brain::Embedded(d) => CtlReply::Windows(d.window_lines()),
|
||||
Brain::Linked(_) => CtlReply::Error("el Cerebro es externo".into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Traduce los comandos del Cerebro a operaciones y las ejecuta.
|
||||
fn apply_commands(&mut self, cmds: Vec<BrainCommand>) {
|
||||
for cmd in cmds {
|
||||
@@ -493,6 +518,25 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("mirada-compositor · vigilando el keymap (recarga en caliente).");
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Brain::Embedded(_) => {
|
||||
let path = mirada_brain::ctl::default_socket_path();
|
||||
match CtlServer::bind(&path) {
|
||||
Ok(s) => {
|
||||
println!("mirada-compositor · API de control en {}", path.display());
|
||||
Some(s)
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("mirada-compositor · sin API de control: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Brain::Linked(_) => None,
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -611,6 +655,18 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
}
|
||||
|
||||
// 2 ter · Peticiones del API de control (mirada-ctl).
|
||||
if let Some(ctl) = &ctl {
|
||||
while let Some(mut conn) = ctl.poll() {
|
||||
let reply = match conn.read_request() {
|
||||
Ok(Some(req)) => state.serve_ctl(req),
|
||||
Ok(None) => continue,
|
||||
Err(e) => CtlReply::Error(format!("{e}")),
|
||||
};
|
||||
let _ = conn.reply(&reply);
|
||||
}
|
||||
}
|
||||
|
||||
// 3 · Composición de las superficies en sus rectángulos.
|
||||
let size = backend.window_size();
|
||||
let damage: Rectangle<i32, smithay::utils::Physical> = Rectangle::from_size(size);
|
||||
|
||||
Reference in New Issue
Block a user