feat(mirada): nmaster, promover a maestra y smart gaps (estilo dwm)

Tanda de funciones de tiling WM, toda pura (mirada-layout/brain), sin
tocar el protocolo:

- nmaster: LayoutParams.master_count — cuántas ventanas van en el área
  maestra. MasterStack y CenteredMaster apilan N maestras; sin pila, las
  maestras llenan la pantalla. Acciones inc-master/dec-master (Super+,
  Super+.), acotadas 1..9.
- Promover a maestra: Workspace::promote_focused lleva la ventana
  enfocada al puesto 0. Acción promote-to-master (Super+Return).
- Smart gaps: una sola ventana se tesela a sangre, sin margen.

combo_string del compositor canoniza ahora teclas con nombre (Return,
Tab, F5, flechas…) vía xkb::keysym_get_name, no sólo caracteres
imprimibles — sin eso Super+Return no sería un atajo expresable.

Cableado en keymap por defecto, HUD de mirada y mirada-ctl. Verificado
end-to-end con headless-ctl. mirada-layout 26->30, mirada-brain 39->41.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-21 00:45:47 +00:00
parent 8821d34bd5
commit 2dd8ff139e
11 changed files with 228 additions and 42 deletions
+3 -2
View File
@@ -62,8 +62,9 @@ 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+…`: foco `Super+j/k`,
los 7 layouts en `Super+t/m/g/c/r/d/s` (o ciclar con `Super+space`), área
maestra `Super+h/l`, escritorios `Super+1..9`, cerrar `Super+q`. Cierra
la ventana del compositor para salir.
maestra `Super+h/l`, `nmaster` `Super+,/.`, promover a maestra
`Super+Return`, escritorios `Super+1..9`, cerrar `Super+q`. Cierra la
ventana del compositor para salir.
## Atajos de teclado
+16 -4
View File
@@ -403,12 +403,13 @@ fn combo_string(mods: &ModifiersState, sym: Keysym) -> Option<String> {
let name = if key == " " {
"space".to_string()
} else {
// ¿Es un único carácter imprimible? Entonces la tecla es ese carácter.
let mut chars = key.chars();
let c = chars.next()?;
if chars.next().is_some() || !c.is_ascii_graphic() {
return None;
match (chars.next(), chars.next()) {
(Some(c), None) if c.is_ascii_graphic() => c.to_ascii_lowercase().to_string(),
// Si no, una tecla con nombre: Return, Tab, Up, F5…
_ => named_key(sym)?,
}
c.to_ascii_lowercase().to_string()
};
let mut combo = String::new();
if mods.logo {
@@ -427,6 +428,17 @@ fn combo_string(mods: &ModifiersState, sym: Keysym) -> Option<String> {
Some(combo)
}
/// El nombre canónico de una tecla especial — `Return`, `Tab`, `Up`,
/// `F5`… `None` si xkb no le da un nombre razonable.
fn named_key(sym: Keysym) -> Option<String> {
let name = xkb::keysym_get_name(sym);
if name.is_empty() || name == "NoSymbol" || name.starts_with("0x") {
None
} else {
Some(name)
}
}
/// Despacha los callbacks de frame de un árbol de superficies: avisa a
/// cada cliente de que puede dibujar el siguiente cuadro.
fn send_frames_surface_tree(surface: &WlSurface, time: u32) {
+2
View File
@@ -125,6 +125,8 @@ Acciones de mirada-ctl:
grid · columns · rows · monocle
grow-master agranda el área de la ventana maestra
shrink-master la encoge
inc-master / dec-master nº de ventanas en el área maestra (nmaster)
promote-to-master la ventana enfocada al puesto maestro
workspace <n> activa el escritorio n (1..9)
send-to-workspace <n> manda la enfocada al escritorio n
quit apaga el compositor
+5 -1
View File
@@ -22,7 +22,8 @@
//! n abre una ventana tab / espacio cicla layout
//! w cierra la enfocada t m g c r d s layout directo
//! j / k foco siguiente/anterior h / l área maestra /+
//! Shift+j / k mueve la enfocada 1..9 ir a escritorio
//! Shift+j / k mueve la enfocada , / . nmaster /+
//! Enter promueve a maestra 1..9 ir a escritorio
//! Ctrl+1..9 enviar a escritorio
//! ```
//!
@@ -291,6 +292,9 @@ impl Mirada {
"s" => self.act(DesktopAction::SetLayout(LayoutMode::Spiral)),
"h" => self.act(DesktopAction::ShrinkMaster),
"l" => self.act(DesktopAction::GrowMaster),
"enter" => self.act(DesktopAction::PromoteToMaster),
"," => self.act(DesktopAction::IncMaster),
"." => self.act(DesktopAction::DecMaster),
d if d.len() == 1 && d.as_bytes()[0].is_ascii_digit() && d != "0" => {
let n = (d.as_bytes()[0] - b'1') as usize;
if ctrl {