feat(mirada): keymap configurable en RON, recargable en caliente
Los atajos de teclado dejan de estar cableados: ahora son un Keymap
configurable que vive sólo en el Cerebro. El Cuerpo nunca lo ve — sólo
recibe la lista de cadenas a interceptar (GrabKeys) y devuelve la
pulsada; es Desktop quien la traduce. Esa separación (qué interceptar
vs. qué significa) hace innecesario cualquier candado o Arc.
mirada-brain:
- keymap.rs — Keymap: from_ron/to_ron, load/save, load_or_init (escribe
un archivo por defecto documentado si falta; default sin pisar si está
corrupto), default_path (~/.config/mirada/keymap.ron), y watch sobre
notify para la recarga en caliente (KeymapWatch).
- DesktopAction: Display + FromStr — vocabulario textual estable
("focus-next", "layout:grid", "workspace:3"); evita los guiones que
romperían el RON de un enum.
- Desktop: with_keymap, set_keymap (cambio en caliente -> nuevo GrabKeys).
- Ejemplo keymap-default: imprime el archivo por defecto en RON.
Apps: mirada y mirada-compositor (modo embebido) cargan el keymap del
usuario al arrancar y lo recargan en caliente cuando el archivo cambia.
Disco RON, cable postcard (sólo la lista de cadenas), sin ejecutable
configurador. mirada-brain: 17 -> 29 tests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -60,10 +60,26 @@ WAYLAND_DISPLAY=wayland-1 foot # o weston-terminal, alacritty, …
|
||||
```
|
||||
|
||||
Las ventanas se teselan solas. El teclado, con la ventana del compositor
|
||||
enfocada, maneja el escritorio con atajos `Super+…` (los que registra el
|
||||
Cerebro: foco `Super+j/k`, layout `Super+Tab`, escritorios `Super+1..9`).
|
||||
enfocada, maneja el escritorio con atajos `Super+…`: foco `Super+j/k`,
|
||||
ciclar layout `Super+space`, escritorios `Super+1..9`, cerrar `Super+q`.
|
||||
Cierra la ventana del compositor para salir.
|
||||
|
||||
## Atajos de teclado
|
||||
|
||||
Los atajos son configurables en RON: `~/.config/mirada/keymap.ron`. En
|
||||
modo autónomo, el Cuerpo lo carga al arrancar (si no existe, escribe uno
|
||||
por defecto documentado) y lo **recarga en caliente** — edita el archivo,
|
||||
guarda, y los atajos cambian sin reiniciar. En modo enlazado el keymap es
|
||||
asunto del Cerebro (la app `mirada`).
|
||||
|
||||
```sh
|
||||
cargo run -p mirada-brain --example keymap-default # ver el formato
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 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};
|
||||
use mirada_brain::{BodyEvent, BrainCommand, Desktop, Keymap};
|
||||
use mirada_link::BodyLink;
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
@@ -435,6 +435,10 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut seat_state = SeatState::new();
|
||||
let seat = seat_state.new_wl_seat(&dh, "mirada");
|
||||
|
||||
// El keymap del usuario (`~/.config/mirada/keymap.ron`). Sólo lo usa
|
||||
// el Cerebro embebido; con un Cerebro enlazado, el keymap es asunto suyo.
|
||||
let keymap_path = Keymap::default_path();
|
||||
|
||||
// Elige el Cerebro: enlazado si `MIRADA_SOCKET` está puesto.
|
||||
let brain = match std::env::var("MIRADA_SOCKET") {
|
||||
Ok(path) => {
|
||||
@@ -445,7 +449,11 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
Err(_) => {
|
||||
println!("mirada-compositor · modo autónomo (Cerebro embebido).");
|
||||
Brain::Embedded(Desktop::new())
|
||||
let keymap = match &keymap_path {
|
||||
Some(p) => Keymap::load_or_init(p),
|
||||
None => Keymap::default(),
|
||||
};
|
||||
Brain::Embedded(Desktop::with_keymap(keymap))
|
||||
}
|
||||
};
|
||||
|
||||
@@ -475,6 +483,16 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
state.apply_commands(vec![grab]);
|
||||
}
|
||||
|
||||
// Vigilancia del keymap para recargarlo en caliente — sólo tiene
|
||||
// sentido con el Cerebro embebido.
|
||||
let keymap_watch = match (&state.brain, &keymap_path) {
|
||||
(Brain::Embedded(_), Some(p)) => Keymap::watch(p).ok(),
|
||||
_ => None,
|
||||
};
|
||||
if keymap_watch.is_some() {
|
||||
println!("mirada-compositor · vigilando el keymap (recarga en caliente).");
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -571,6 +589,28 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 2 · Comandos de un Cerebro enlazado.
|
||||
state.brain_poll();
|
||||
|
||||
// 2 bis · Recarga del keymap si el archivo cambió en disco.
|
||||
if keymap_watch.as_ref().is_some_and(|w| w.changed()) {
|
||||
if let Some(path) = &keymap_path {
|
||||
match Keymap::load(path) {
|
||||
Ok(km) => {
|
||||
let cmd = if let Brain::Embedded(d) = &mut state.brain {
|
||||
Some(d.set_keymap(km))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(cmd) = cmd {
|
||||
state.apply_commands(vec![cmd]);
|
||||
}
|
||||
println!("mirada-compositor · keymap recargado.");
|
||||
}
|
||||
Err(e) => eprintln!(
|
||||
"mirada-compositor · keymap inválido, conservo el anterior: {e}"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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