feat(mirada): acople del shell — ventana-dock al pie de la pantalla
Fase 2 del plan «shell»: carmen reconoce la ventana del shell y le
reserva su sitio, en vez de teselarla como una más.
Una ventana cuyo `app_id` es `carmen.shell` no entra en el teselado:
carmen le reserva una franja de 40 px al pie de la salida, la dimensiona
y la fija ahí, y la compone sobre todas las demás. El Cerebro tesela el
resto de ventanas en el área que queda.
- `mirada-protocol`: nuevo `BodyEvent::OutputResized { id, w, h }` — el
Cerebro cambia el área útil de una salida **sin** perder el escritorio
que muestra (a diferencia de quitar y volver a añadir la salida — que,
de paso, era un bug latente al redimensionar la ventana winit).
- `mirada-brain`: `Desktop` atiende `OutputResized` (test nuevo).
- `mirada-body`: `BodyState::resize_output`.
- `mirada-compositor`: `ManagedWindow.is_shell`, `App.output_size`,
`dock_shell`/`output_changed`; `register_toplevel` no registra el
shell en el Cerebro; al cerrarse libera la franja. El shell se compone
y se enfoca con el ratón aunque no viva en el Cerebro; no lleva marco.
El backend winit usa ahora `resize_output` al redimensionar.
GPUI no habla `wlr-layer-shell`, así que el acople es por `app_id`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -228,14 +228,21 @@ Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET`
|
||||
- **Sesión** — `~/.config/mirada/autostart` (un comando por línea) se
|
||||
lanza al arrancar el backend DRM; el script `session/mirada-session` y
|
||||
`session/carmen.desktop` integran carmen con un gestor de login.
|
||||
- **Acople del shell** — una ventana con `app_id = "carmen.shell"` no se
|
||||
tesela: carmen le reserva una franja de 40 px al pie de la salida y la
|
||||
pinta sobre todo. La reserva viaja como `BodyEvent::OutputResized`, que
|
||||
encoge el área útil del Cerebro **sin** perder el escritorio que
|
||||
muestra (a diferencia de quitar y volver a añadir la salida). Es el
|
||||
anclaje de la futura `shuma-shell` en modo launcher.
|
||||
|
||||
**Pendiente** — refinamientos del Cuerpo:
|
||||
|
||||
| capa pendiente | rol |
|
||||
| ---------------- | ------------------------------------------------------------ |
|
||||
| capa pendiente | rol |
|
||||
| ------------------ | ---------------------------------------------------------- |
|
||||
| puntero en `winit` | ratón en el backend anidado (hoy sólo el backend DRM) |
|
||||
| `mirada-input` | repetición de teclas, gestos; hotplug de monitores |
|
||||
| barra de estado | `wlr-layer-shell` + un cliente que dibuje la barra |
|
||||
| `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` |
|
||||
| `mirada-input` | repetición de teclas, gestos; hotplug de monitores |
|
||||
| `shuma-shell` | modo launcher: barra + input + cajón sobre el acople shell |
|
||||
| `wlr-layer-shell` | barras externas tipo waybar, fondos, notificaciones |
|
||||
| `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` |
|
||||
|
||||
CRIU (congelar/restaurar ventanas) queda anotado como futuro.
|
||||
|
||||
@@ -184,6 +184,17 @@ impl BodyState {
|
||||
BodyEvent::OutputRemoved { id }
|
||||
}
|
||||
|
||||
/// Cambia el área útil de una salida sin desconectarla — al
|
||||
/// redimensionar la ventana anfitriona o al reservar/liberar la
|
||||
/// franja del shell. Conserva el escritorio que muestra.
|
||||
pub fn resize_output(&mut self, id: OutputId, width: i32, height: i32) -> BodyEvent {
|
||||
if let Some((_, rect)) = self.outputs.iter_mut().find(|(o, _)| *o == id) {
|
||||
rect.w = width;
|
||||
rect.h = height;
|
||||
}
|
||||
BodyEvent::OutputResized { id, width, height }
|
||||
}
|
||||
|
||||
/// Registra una superficie recién creada por un cliente.
|
||||
pub fn open_surface(
|
||||
&mut self,
|
||||
|
||||
@@ -141,6 +141,18 @@ impl Desktop {
|
||||
self.reflow_outputs();
|
||||
self.relayout()
|
||||
}
|
||||
BodyEvent::OutputResized { id, width, height } => {
|
||||
// Sólo cambia el área útil; el escritorio que muestra la
|
||||
// salida se conserva.
|
||||
if let Some(o) = self.outputs.iter_mut().find(|o| o.id == id) {
|
||||
o.rect.w = width;
|
||||
o.rect.h = height;
|
||||
self.reflow_outputs();
|
||||
self.relayout()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
BodyEvent::WindowOpened { id, app_id, title } => {
|
||||
// Las reglas pueden mandarla a otro escritorio o hacerla flotar.
|
||||
let outcome = self.rules.resolve(&app_id, &title);
|
||||
@@ -877,6 +889,23 @@ mod tests {
|
||||
assert_eq!(p.rect, target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resizing_an_output_retiles_without_losing_the_workspace() {
|
||||
let mut d = desktop_with_screen();
|
||||
open(&mut d, 1);
|
||||
d.on_event(BodyEvent::Keybind("Super+2".into())); // escritorio activo → 2
|
||||
assert_eq!(d.active_index(), 1);
|
||||
let cmds = d.on_event(BodyEvent::OutputResized {
|
||||
id: 0,
|
||||
width: 1920,
|
||||
height: 1040,
|
||||
});
|
||||
// A diferencia de quitar y volver a añadir la salida, el
|
||||
// escritorio activo se conserva.
|
||||
assert_eq!(d.active_index(), 1);
|
||||
assert!(matches!(cmds.as_slice(), [BrainCommand::Place(_)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_spawn_keybind_becomes_a_spawn_command() {
|
||||
let mut d = desktop_with_screen();
|
||||
|
||||
@@ -82,6 +82,11 @@ pub enum BodyEvent {
|
||||
OutputAdded { id: OutputId, width: i32, height: i32 },
|
||||
/// Desapareció un monitor.
|
||||
OutputRemoved { id: OutputId },
|
||||
/// Cambió el área útil de un monitor — porque se redimensionó la
|
||||
/// ventana anfitriona, o porque el shell reservó o liberó su franja.
|
||||
/// El escritorio que muestra **no** cambia (a diferencia de quitar y
|
||||
/// volver a añadir la salida).
|
||||
OutputResized { id: OutputId, width: i32, height: i32 },
|
||||
/// Un cliente creó una ventana de nivel superior.
|
||||
WindowOpened { id: WindowId, app_id: String, title: String },
|
||||
/// Una ventana se cerró (por el cliente o tras un [`BrainCommand::Close`]).
|
||||
|
||||
Reference in New Issue
Block a user