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:
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "mirada-ctl"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
publish.workspace = true
|
||||
description = "mirada-ctl — control del compositor carmen por línea de comandos (estilo swaymsg/hyprctl): aplica acciones de escritorio y consulta ventanas vía el socket de control de mirada-brain."
|
||||
|
||||
[[bin]]
|
||||
name = "mirada-ctl"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
mirada-brain = { path = "../../modules/mirada/mirada-brain" }
|
||||
@@ -0,0 +1,126 @@
|
||||
//! `mirada-ctl` — el control del compositor carmen por línea de comandos.
|
||||
//!
|
||||
//! Al estilo de `swaymsg` / `hyprctl`: dispara una acción de escritorio o
|
||||
//! consulta el estado, hablando con el Cerebro por su socket de control
|
||||
//! ([`mirada_brain::ctl`]). El Cerebro es la app `mirada`, o
|
||||
//! `mirada-compositor` cuando lleva el Cerebro embebido.
|
||||
//!
|
||||
//! ```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 layout grid # fija el modo de teselado
|
||||
//! mirada-ctl windows # lista las ventanas
|
||||
//! mirada-ctl actions # lista las acciones
|
||||
//! ```
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
use mirada_brain::ctl::{self, CtlReply, CtlRequest, WindowLine};
|
||||
use mirada_brain::DesktopAction;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
match run(&args) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(msg) => {
|
||||
eprintln!("mirada-ctl: {msg}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run(args: &[String]) -> Result<(), String> {
|
||||
match args.first().map(String::as_str) {
|
||||
None | Some("-h" | "--help" | "help") => {
|
||||
print_help();
|
||||
Ok(())
|
||||
}
|
||||
Some("actions") => {
|
||||
print_actions();
|
||||
Ok(())
|
||||
}
|
||||
Some("windows") => match request(CtlRequest::ListWindows)? {
|
||||
CtlReply::Windows(ws) => {
|
||||
print_windows(&ws);
|
||||
Ok(())
|
||||
}
|
||||
CtlReply::Error(e) => Err(e),
|
||||
CtlReply::Ok => Err("respuesta inesperada del Cerebro".into()),
|
||||
},
|
||||
// Todo lo demás es una acción. `focus-window 5` y `workspace 3`
|
||||
// se unen con `:` a la forma canónica (`focus-window:5`).
|
||||
Some(_) => {
|
||||
let spec = args.join(":");
|
||||
let action: DesktopAction = spec
|
||||
.parse()
|
||||
.map_err(|e| format!("{e}\n lista de acciones: mirada-ctl actions"))?;
|
||||
match request(CtlRequest::Do(action))? {
|
||||
CtlReply::Ok => Ok(()),
|
||||
CtlReply::Error(e) => Err(e),
|
||||
CtlReply::Windows(_) => Err("respuesta inesperada del Cerebro".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manda una petición al Cerebro y devuelve su respuesta.
|
||||
fn request(req: CtlRequest) -> Result<CtlReply, String> {
|
||||
let path = ctl::default_socket_path();
|
||||
ctl::send_request(&path, &req).map_err(|e| {
|
||||
format!(
|
||||
"no pude hablar con el Cerebro en {} ({e})\n \
|
||||
¿está corriendo `mirada` o `mirada-compositor`?",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Imprime la lista de ventanas, marcando la enfocada con `*`.
|
||||
fn print_windows(windows: &[WindowLine]) {
|
||||
if windows.is_empty() {
|
||||
println!("(no hay ventanas)");
|
||||
return;
|
||||
}
|
||||
for w in windows {
|
||||
let mark = if w.focused { '*' } else { ' ' };
|
||||
println!(
|
||||
"{mark} id {:<4} esc {} {:<24} {}",
|
||||
w.id, w.workspace, w.app_id, w.title
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!(
|
||||
"mirada-ctl — control del compositor carmen\n\
|
||||
\n\
|
||||
USO:\n \
|
||||
mirada-ctl <acción> aplica una acción de escritorio\n \
|
||||
mirada-ctl windows lista las ventanas\n \
|
||||
mirada-ctl actions lista las acciones disponibles\n\
|
||||
\n\
|
||||
EJEMPLOS:\n \
|
||||
mirada-ctl focus-next\n \
|
||||
mirada-ctl focus-window 5\n \
|
||||
mirada-ctl workspace 3\n \
|
||||
mirada-ctl layout grid"
|
||||
);
|
||||
}
|
||||
|
||||
fn print_actions() {
|
||||
println!(
|
||||
"Acciones de mirada-ctl:\n \
|
||||
focus-next mueve el foco a la siguiente ventana\n \
|
||||
focus-prev mueve el foco a la anterior\n \
|
||||
focus-window <id> enfoca la ventana <id> (ver: mirada-ctl windows)\n \
|
||||
move-forward adelanta la ventana enfocada en el teselado\n \
|
||||
move-backward la atrasa\n \
|
||||
close-focused cierra la ventana enfocada\n \
|
||||
cycle-layout pasa al siguiente modo de teselado\n \
|
||||
layout <modo> master-stack | monocle | grid | columns\n \
|
||||
workspace <n> activa el escritorio n (1..9)\n \
|
||||
send-to-workspace <n> manda la enfocada al escritorio n\n \
|
||||
quit apaga el compositor"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user