gioser

tawasuyu/brahman

Rust 483TOML 191Markdown 50JSON 176798 Definiciones

feat(mirada): pantalla completa real — toggle-fullscreen

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>

be61ddb6eb441193c4374a243fbb00aed47b42de · por sergio · (hace 5 meses) · padre 6dfd9e62a · ver el árbol en este commit

Delta semántico · 27

crates/apps/mirada-compositor/src/main.rs

crates/apps/mirada-ctl/src/main.rs

crates/apps/mirada/src/main.rs

crates/modules/mirada/mirada-body/src/lib.rs

crates/modules/mirada/mirada-brain/examples/headless-ctl.rs

  • cambió por dentro fn main

crates/modules/mirada/mirada-brain/src/action.rs

crates/modules/mirada/mirada-brain/src/desktop.rs

crates/modules/mirada/mirada-brain/src/keymap.rs

crates/modules/mirada/mirada-layout/src/workspace.rs

crates/modules/mirada/mirada-protocol/src/lib.rs

12 archivos · +133 −11

diff --git a/crates/apps/mirada-compositor/src/main.rs b/crates/apps/mirada-compositor/src/main.rs
index 435b928..090f05a 100644
--- a/crates/apps/mirada-compositor/src/main.rs
+++ b/crates/apps/mirada-compositor/src/main.rs
@@ -174,13 +174,18 @@ impl App {
     /// Ejecuta una operación concreta sobre las superficies reales.
     fn exec_op(&mut self, op: BodyOp) {
         match op {
-            BodyOp::Configure { id, rect, visible, floating } => {
+            BodyOp::Configure { id, rect, visible, floating, fullscreen } => {
                 if let Some(w) = self.windows.iter_mut().find(|w| w.id == id) {
                     w.loc = (rect.x, rect.y);
                     w.visible = visible;
                     w.floating = floating;
                     w.toplevel.with_pending_state(|s| {
                         s.size = Some((rect.w.max(1), rect.h.max(1)).into());
+                        if fullscreen {
+                            s.states.set(xdg_toplevel::State::Fullscreen);
+                        } else {
+                            s.states.unset(xdg_toplevel::State::Fullscreen);
+                        }
                     });
                     w.toplevel.send_pending_configure();
                 }
diff --git a/crates/apps/mirada-ctl/src/main.rs b/crates/apps/mirada-ctl/src/main.rs
index 0f596ea..3185a1a 100644
--- a/crates/apps/mirada-ctl/src/main.rs
+++ b/crates/apps/mirada-ctl/src/main.rs
@@ -121,6 +121,7 @@ Acciones de mirada-ctl:
   move-backward              la atrasa
   close-focused              cierra la ventana enfocada
   toggle-float               alterna flotante / teselada la enfocada
+  toggle-fullscreen          alterna pantalla completa en la enfocada
   cycle-layout               pasa al siguiente modo de teselado
   layout <modo>              master-stack · centered-master · spiral
                              grid · columns · rows · monocle
diff --git a/crates/apps/mirada/src/main.rs b/crates/apps/mirada/src/main.rs
index 6f3cfe8..71c0476 100644
--- a/crates/apps/mirada/src/main.rs
+++ b/crates/apps/mirada/src/main.rs
@@ -21,7 +21,7 @@
 //! ```text
 //!   n            abre una ventana          tab / espacio  cicla layout
 //!   w            cierra la enfocada        t m g c r d s  layout directo
-//!   f            flota / tesela            h / l          área maestra −/+
+//!   f / Shift+f  flota / pantalla completa h / l          área maestra −/+
 //!   j / k        foco siguiente/anterior   , / .          nmaster −/+
 //!   Shift+j / k  mueve la enfocada         1..9           ir a escritorio
 //!   Enter        promueve a maestra        Ctrl+1..9      enviar a escritorio
@@ -283,6 +283,7 @@ impl Mirada {
         match ks.key.as_str() {
             "n" if !connected => self.open_window(),
             "w" => self.act(DesktopAction::CloseFocused),
+            "f" if shift => self.act(DesktopAction::ToggleFullscreen),
             "f" => self.act(DesktopAction::ToggleFloat),
             "j" if shift => self.act(DesktopAction::MoveForward),
             "k" if shift => self.act(DesktopAction::MoveBackward),
@@ -454,7 +455,9 @@ impl Render for Mirada {
             let tb_bg = if p.focused { theme.accent } else { theme.bg_row_hover };
             let tb_fg = if p.focused { on_accent } else { theme.fg_muted };
             let pid = p.id;
-            let kind_label = if p.floating {
+            let kind_label = if p.fullscreen {
+                "· pantalla completa ·"
+            } else if p.floating {
                 "· ventana flotante ·"
             } else {
                 "· superficie del Cuerpo ·"
diff --git a/crates/modules/mirada/SDD.md b/crates/modules/mirada/SDD.md
index 3056470..657de06 100644
--- a/crates/modules/mirada/SDD.md
+++ b/crates/modules/mirada/SDD.md
@@ -118,6 +118,10 @@ que un front-end (`Keybind` → `lookup` → `apply`); hay otros tres:
   flotantes aparte, `layout()` las pone al final y `WindowPlacement`
   /`BodyOp::Configure` llevan `floating: bool` para que el Cuerpo las
   componga por encima.
+- **Pantalla completa** — `ToggleFullscreen` (`Super+Shift+f`): la ventana
+  cubre toda la salida (sin gap), oculta al resto y se lleva el foco;
+  `Workspace.fullscreen: Option<WindowId>`, y el Cuerpo le fija el estado
+  `xdg_toplevel Fullscreen`.
 - **Layout y área maestra por el API** — los 7 modos se intercambian
   (`SetLayout`/`CycleLayout`, `mirada-ctl layout spiral`); el área
   maestra se redimensiona (`grow`/`shrink-master`, `Super+l`/`Super+h`);
@@ -160,7 +164,7 @@ a las ya abiertas.
 ## Estado
 
 Implementado y verde: `mirada-layout` (32 tests), `mirada-protocol`
-(10), `mirada-brain` (51), `mirada-link` (7), `mirada-body` (14), las
+(11), `mirada-brain` (52), `mirada-link` (7), `mirada-body` (14), las
 apps `mirada` y `mirada-compositor` (compilan; verificación visual
 manual) y `mirada-ctl` (CLI, probado vía el ejemplo `headless-ctl`).
 
diff --git a/crates/modules/mirada/mirada-body/src/lib.rs b/crates/modules/mirada/mirada-body/src/lib.rs
index f5060c4..8d06577 100644
--- a/crates/modules/mirada/mirada-body/src/lib.rs
+++ b/crates/modules/mirada/mirada-body/src/lib.rs
@@ -31,6 +31,8 @@ pub struct Surface {
     pub focused: bool,
     /// `true` si flota: el backend la pinta por encima de las teseladas.
     pub floating: bool,
+    /// `true` si está en pantalla completa.
+    pub fullscreen: bool,
 }
 
 impl Surface {
@@ -42,6 +44,7 @@ impl Surface {
             visible: false,
             focused: false,
             floating: false,
+            fullscreen: false,
         }
     }
 }
@@ -49,9 +52,16 @@ impl Surface {
 /// Una orden concreta para el backend (smithay, headless, …).
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub enum BodyOp {
-    /// Recoloca una superficie, la muestra u oculta y dice si flota
-    /// (las flotantes se componen por encima de las teseladas).
-    Configure { id: WindowId, rect: Rect, visible: bool, floating: bool },
+    /// Recoloca una superficie, la muestra u oculta y dice si flota o
+    /// está en pantalla completa (el backend ajusta el orden de pintado
+    /// y el estado `xdg_toplevel` en consecuencia).
+    Configure {
+        id: WindowId,
+        rect: Rect,
+        visible: bool,
+        floating: bool,
+        fullscreen: bool,
+    },
     /// Da el foco del teclado a una superficie.
     Focus(WindowId),
     /// Quita el foco a todas las superficies.
@@ -104,15 +114,18 @@ impl BodyState {
                         if s.geometry != Some(p.rect)
                             || s.visible != p.visible
                             || s.floating != p.floating
+                            || s.fullscreen != p.fullscreen
                         {
                             s.geometry = Some(p.rect);
                             s.visible = p.visible;
                             s.floating = p.floating;
+                            s.fullscreen = p.fullscreen;
                             ops.push(BodyOp::Configure {
                                 id: p.id,
                                 rect: p.rect,
                                 visible: p.visible,
                                 floating: p.floating,
+                                fullscreen: p.fullscreen,
                             });
                         }
                     }
@@ -128,6 +141,7 @@ impl BodyState {
                             rect,
                             visible: false,
                             floating: s.floating,
+                            fullscreen: s.fullscreen,
                         });
                     }
                 }
@@ -248,6 +262,7 @@ mod tests {
             visible,
             focused,
             floating: false,
+            fullscreen: false,
         }
     }
 
@@ -310,6 +325,7 @@ mod tests {
             rect: Rect::new(0, 0, 800, 600),
             visible: false,
             floating: false,
+            fullscreen: false,
         }));
         assert!(!b.surface(2).unwrap().visible);
     }
diff --git a/crates/modules/mirada/mirada-brain/examples/headless-ctl.rs b/crates/modules/mirada/mirada-brain/examples/headless-ctl.rs
index 19702d6..3129bf0 100644
--- a/crates/modules/mirada/mirada-brain/examples/headless-ctl.rs
+++ b/crates/modules/mirada/mirada-brain/examples/headless-ctl.rs
@@ -60,7 +60,13 @@ fn main() {
                                             p.rect.h,
                                             p.rect.x,
                                             p.rect.y,
-                                            if p.floating { "  ~flotante" } else { "" },
+                                            if p.fullscreen {
+                                                "  ~pantalla"
+                                            } else if p.floating {
+                                                "  ~flotante"
+                                            } else {
+                                                ""
+                                            },
                                             if p.focused { "  *" } else { "" },
                                         );
                                     }
diff --git a/crates/modules/mirada/mirada-brain/src/action.rs b/crates/modules/mirada/mirada-brain/src/action.rs
index 438cc54..819b36b 100644
--- a/crates/modules/mirada/mirada-brain/src/action.rs
+++ b/crates/modules/mirada/mirada-brain/src/action.rs
@@ -40,6 +40,8 @@ pub enum DesktopAction {
     CloseFocused,
     /// Alterna entre flotante y teselada la ventana enfocada.
     ToggleFloat,
+    /// Alterna pantalla completa en la ventana enfocada.
+    ToggleFullscreen,
     /// Pasa al siguiente modo de teselado.
     CycleLayout,
     /// Fija un modo de teselado concreto.
@@ -101,6 +103,7 @@ impl fmt::Display for DesktopAction {
             DesktopAction::MoveBackward => f.write_str("move-backward"),
             DesktopAction::CloseFocused => f.write_str("close-focused"),
             DesktopAction::ToggleFloat => f.write_str("toggle-float"),
+            DesktopAction::ToggleFullscreen => f.write_str("toggle-fullscreen"),
             DesktopAction::CycleLayout => f.write_str("cycle-layout"),
             DesktopAction::SetLayout(m) => write!(f, "layout:{}", layout_slug(*m)),
             DesktopAction::GrowMaster => f.write_str("grow-master"),
@@ -129,6 +132,7 @@ impl FromStr for DesktopAction {
             "move-backward" => Self::MoveBackward,
             "close-focused" => Self::CloseFocused,
             "toggle-float" => Self::ToggleFloat,
+            "toggle-fullscreen" => Self::ToggleFullscreen,
             "cycle-layout" => Self::CycleLayout,
             "grow-master" => Self::GrowMaster,
             "shrink-master" => Self::ShrinkMaster,
@@ -188,6 +192,7 @@ pub fn default_keymap() -> Vec<(String, DesktopAction)> {
         ("Super+Shift+k".into(), DesktopAction::MoveBackward),
         ("Super+q".into(), DesktopAction::CloseFocused),
         ("Super+f".into(), DesktopAction::ToggleFloat),
+        ("Super+Shift+f".into(), DesktopAction::ToggleFullscreen),
         ("Super+space".into(), DesktopAction::CycleLayout),
         ("Super+t".into(), DesktopAction::SetLayout(LayoutMode::MasterStack)),
         ("Super+m".into(), DesktopAction::SetLayout(LayoutMode::Monocle)),
diff --git a/crates/modules/mirada/mirada-brain/src/desktop.rs b/crates/modules/mirada/mirada-brain/src/desktop.rs
index f8e4a63..b7d5a6d 100644
--- a/crates/modules/mirada/mirada-brain/src/desktop.rs
+++ b/crates/modules/mirada/mirada-brain/src/desktop.rs
@@ -218,6 +218,18 @@ impl Desktop {
                 }
                 self.relayout()
             }
+            DesktopAction::ToggleFullscreen => {
+                let Some(id) = self.workspaces[self.active].focused() else {
+                    return Vec::new();
+                };
+                let ws = &mut self.workspaces[self.active];
+                if ws.fullscreen() == Some(id) {
+                    ws.set_fullscreen(None);
+                } else {
+                    ws.set_fullscreen(Some(id));
+                }
+                self.relayout()
+            }
             DesktopAction::CycleLayout => {
                 let next = self.workspaces[self.active].params().mode.next();
                 self.workspaces[self.active].set_mode(next);
@@ -475,6 +487,23 @@ mod tests {
         assert!(!places(&cmds).iter().find(|x| x.id == 2).unwrap().floating);
     }
 
+    #[test]
+    fn toggle_fullscreen_covers_the_screen_and_hides_the_rest() {
+        let mut d = desktop_with_screen();
+        for id in [1, 2, 3] {
+            open(&mut d, id);
+        }
+        let cmds = d.apply(DesktopAction::ToggleFullscreen); // sobre la 3
+        let p = places(&cmds);
+        let fs = p.iter().find(|x| x.id == 3).unwrap();
+        assert!(fs.fullscreen && fs.visible);
+        assert_eq!(fs.rect, d.screen().unwrap());
+        assert!(p.iter().filter(|x| x.id != 3).all(|x| !x.visible));
+        // Alternar de nuevo restaura el teselado: las tres visibles.
+        let cmds = d.apply(DesktopAction::ToggleFullscreen);
+        assert_eq!(places(&cmds).iter().filter(|x| x.visible).count(), 3);
+    }
+
     #[test]
     fn a_rule_sends_a_new_window_to_its_workspace() {
         let mut d = desktop_with_screen();
diff --git a/crates/modules/mirada/mirada-brain/src/keymap.rs b/crates/modules/mirada/mirada-brain/src/keymap.rs
index 918e7ae..f59985e 100644
--- a/crates/modules/mirada/mirada-brain/src/keymap.rs
+++ b/crates/modules/mirada/mirada-brain/src/keymap.rs
@@ -238,6 +238,7 @@ const KEYMAP_HEADER: &str = "\
 //   move-forward / move-backward     reordena la ventana enfocada
 //   close-focused                    cierra la enfocada
 //   toggle-float                     alterna flotante / teselada
+//   toggle-fullscreen                alterna pantalla completa
 //   cycle-layout                     siguiente modo de teselado
 //   layout:<modo>                    master-stack | centered-master | spiral
 //                                    grid | columns | rows | monocle
diff --git a/crates/modules/mirada/mirada-layout/src/workspace.rs b/crates/modules/mirada/mirada-layout/src/workspace.rs
index 938a362..dbead27 100644
--- a/crates/modules/mirada/mirada-layout/src/workspace.rs
+++ b/crates/modules/mirada/mirada-layout/src/workspace.rs
@@ -21,6 +21,9 @@ pub struct Workspace {
     /// Ventanas flotantes y su rectángulo: salen del teselado y se pintan
     /// encima. Las que no están aquí se teselan normalmente.
     floating: BTreeMap<WindowId, Rect>,
+    /// La ventana en pantalla completa, si hay alguna: cubre toda la
+    /// salida y oculta al resto.
+    fullscreen: Option<WindowId>,
 }
 
 impl Workspace {
@@ -31,6 +34,7 @@ impl Workspace {
             focus: 0,
             params,
             floating: BTreeMap::new(),
+            fullscreen: None,
         }
     }
 
@@ -84,6 +88,9 @@ impl Workspace {
         };
         self.windows.remove(i);
         self.floating.remove(&window);
+        if self.fullscreen == Some(window) {
+            self.fullscreen = None;
+        }
         if i < self.focus {
             self.focus -= 1;
         }
@@ -111,6 +118,16 @@ impl Workspace {
         self.floating.contains_key(&window)
     }
 
+    /// La ventana en pantalla completa de este escritorio, si hay alguna.
+    pub fn fullscreen(&self) -> Option<WindowId> {
+        self.fullscreen
+    }
+
+    /// Pone (o quita, con `None`) la ventana en pantalla completa.
+    pub fn set_fullscreen(&mut self, window: Option<WindowId>) {
+        self.fullscreen = window;
+    }
+
     /// Ventana enfocada, o `None` si el escritorio está vacío.
     pub fn focused(&self) -> Option<WindowId> {
         self.windows.get(self.focus).copied()
diff --git a/crates/modules/mirada/mirada-protocol/src/lib.rs b/crates/modules/mirada/mirada-protocol/src/lib.rs
index 58701e4..a8e9169 100644
--- a/crates/modules/mirada/mirada-protocol/src/lib.rs
+++ b/crates/modules/mirada/mirada-protocol/src/lib.rs
@@ -48,6 +48,8 @@ pub struct WindowPlacement {
     pub focused: bool,
     /// `true` si flota (fuera del teselado): el Cuerpo la pinta encima.
     pub floating: bool,
+    /// `true` si está en pantalla completa: cubre toda la salida.
+    pub fullscreen: bool,
 }
 
 /// Una orden del Cerebro al Cuerpo.
@@ -140,20 +142,31 @@ pub fn read_frame<R: Read, T: DeserializeOwned>(r: &mut R) -> io::Result<Option<
 /// En modo [`LayoutMode::Monocle`] sólo la ventana enfocada queda
 /// `visible`; en el resto de modos todas lo están.
 pub fn placements(ws: &Workspace, screen: Rect) -> Vec<WindowPlacement> {
+    let fullscreen = ws.fullscreen();
     let monocle = ws.params().mode == LayoutMode::Monocle;
     let focused = ws.focused();
     ws.layout(screen)
         .into_iter()
         .map(|(id, rect)| {
-            let is_focused = focused == Some(id);
             let floating = ws.is_floating(id);
+            let is_fs = fullscreen == Some(id);
+            // Con una ventana en pantalla completa manda ella: ocupa toda
+            // la salida, es la única visible y se lleva el foco.
+            let (rect, visible, is_focused) = match fullscreen {
+                Some(_) => (if is_fs { screen } else { rect }, is_fs, is_fs),
+                None => {
+                    let f = focused == Some(id);
+                    // Una flotante siempre se ve; en `Monocle`, sólo la enfocada.
+                    (rect, floating || !monocle || f, f)
+                }
+            };
             WindowPlacement {
                 id,
                 rect,
-                // Una flotante siempre se ve; en `Monocle`, sólo la enfocada.
-                visible: floating || !monocle || is_focused,
+                visible,
                 focused: is_focused,
                 floating,
+                fullscreen: is_fs,
             }
         })
         .collect()
@@ -183,6 +196,7 @@ mod tests {
             visible: true,
             focused: true,
             floating: false,
+            fullscreen: false,
         }]);
         let mut buf = Vec::new();
         write_frame(&mut buf, &cmd).unwrap();
@@ -277,6 +291,19 @@ mod tests {
         assert_eq!(f.rect, Rect::new(0, 0, 200, 200));
     }
 
+    #[test]
+    fn a_fullscreen_window_covers_the_screen_and_hides_the_rest() {
+        let mut w = ws(LayoutMode::Columns);
+        w.set_fullscreen(Some(20));
+        let p = placements(&w, SCREEN);
+        let fs = p.iter().find(|x| x.id == 20).unwrap();
+        assert!(fs.fullscreen);
+        assert!(fs.focused, "la ventana en pantalla completa se lleva el foco");
+        assert_eq!(fs.rect, SCREEN);
+        // El resto queda oculto.
+        assert!(p.iter().filter(|x| x.id != 20).all(|x| !x.visible));
+    }
+
     #[test]
     fn placements_fill_a_place_command_round_trip() {
         let cmd = BrainCommand::Place(placements(&ws(LayoutMode::Grid), SCREEN));
diff --git a/vamos.txt b/vamos.txt
index 3ec5838..5f02e12 100644
--- a/vamos.txt
+++ b/vamos.txt
@@ -1065,5 +1065,13 @@
 
 
 
+  Pantalla completa — toggle-fullscreen (Super+Shift+f):
+  La ventana enfocada cubre toda la salida (sin gap), oculta al resto y se lleva el foco.
+  Workspace.fullscreen: Option<WindowId>; WindowPlacement/BodyOp::Configure llevan fullscreen: bool;
+  el Cuerpo le fija el estado xdg_toplevel Fullscreen. Alternar de nuevo restaura el teselado.
+  mirada-ctl toggle-fullscreen
+
+
+