diff --git a/Cargo.lock b/Cargo.lock index 897097f..256ff4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12163,6 +12163,7 @@ dependencies = [ "matilda-core", "matilda-discover", "matilda-plan", + "mirada-brain", "nahual-launcher", "nahual-theme", "serde_json", diff --git a/crates/apps/shuma-shell/Cargo.toml b/crates/apps/shuma-shell/Cargo.toml index 62e4488..eb478eb 100644 --- a/crates/apps/shuma-shell/Cargo.toml +++ b/crates/apps/shuma-shell/Cargo.toml @@ -18,6 +18,9 @@ shuma-session = { path = "../../modules/shuma/shuma-session" } shuma-exec = { path = "../../modules/shuma/shuma-exec" } shuma-infer = { path = "../../modules/shuma/shuma-infer" } shuma-sysmon = { path = "../../modules/shuma/shuma-sysmon" } +# El protocolo de control de carmen — el modo launcher lista y enfoca +# ventanas a través de él. +mirada-brain = { path = "../../modules/mirada/mirada-brain" } # Herramienta matilda, embebida en la ventana del shell. matilda-core = { path = "../../modules/matilda/matilda-core" } matilda-plan = { path = "../../modules/matilda/matilda-plan" } diff --git a/crates/apps/shuma-shell/src/main.rs b/crates/apps/shuma-shell/src/main.rs index 595d3ce..f4f5bb3 100644 --- a/crates/apps/shuma-shell/src/main.rs +++ b/crates/apps/shuma-shell/src/main.rs @@ -27,6 +27,8 @@ use gpui::{ MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PathBuilder, Pixels, Render, ScrollHandle, SharedString, Style, Window, WindowBounds, WindowOptions, }; +use mirada_brain::ctl::{default_socket_path, send_request}; +use mirada_brain::{CtlReply, CtlRequest, DesktopAction, WindowLine}; use nahual_launcher::launch_app; use nahual_theme::Theme; use shuma_exec::{run as exec_run, CommandSpec, Exec, RunEvent, RunHandle, StageSpec}; @@ -69,6 +71,23 @@ fn fkey_index(key: &str) -> Option { (1..=8).contains(&n).then_some(n - 1) } +/// Pregunta a carmen, por su socket de control, la lista de ventanas +/// abiertas. `None` si el compositor no está o respondió otra cosa. +fn poll_ctl_windows() -> Option> { + match send_request(&default_socket_path(), &CtlRequest::ListWindows) { + Ok(CtlReply::Windows(w)) => Some(w), + _ => None, + } +} + +/// Pide a carmen que enfoque una ventana del escritorio. +fn focus_window(id: u64) { + let _ = send_request( + &default_socket_path(), + &CtlRequest::Do(DesktopAction::FocusWindow(id)), + ); +} + /// Quita las comillas exteriores de un argumento (`"hola"` → `hola`). fn unquote(arg: &str) -> String { let b = arg.as_bytes(); @@ -340,6 +359,9 @@ struct Shell { /// `true` cuando el shell corre como **modo launcher**: una barra /// compacta acoplada al pie de carmen, en vez del panel completo. launcher: bool, + /// Las ventanas abiertas del escritorio, según el socket de control + /// de carmen — la barra de tareas del modo launcher. + windows_bar: Vec, } impl Shell { @@ -382,6 +404,7 @@ impl Shell { focus: cx.focus_handle(), focused_once: false, launcher: false, + windows_bar: Vec::new(), }; shell.start_loop(cx); shell @@ -396,12 +419,22 @@ impl Shell { cx.background_executor().timer(Duration::from_millis(110)).await; tick += 1; let sysmon = tick % 10 == 0; + // Cada ~1 s pregunta a carmen por sus ventanas. La llamada + // bloquea un instante sobre un socket Unix local — aquí, + // en el executor de fondo, no en el hilo de la UI. + let windows = (tick % 9 == 0).then(poll_ctl_windows).flatten(); let alive = this.update(cx, |shell, cx| { let mut changed = shell.drain_exec(); if sysmon { shell.snapshot = shell.sampler.sample(); changed = true; } + if let Some(w) = windows { + if w != shell.windows_bar { + shell.windows_bar = w; + changed = true; + } + } if changed { cx.notify(); } @@ -1020,15 +1053,63 @@ impl Shell { row } - /// El modo launcher: una barra compacta —glifo, input, estado del - /// último comando— pensada para la franja que carmen reserva al pie. + /// El modo launcher: una barra compacta —glifo, input, barra de + /// ventanas, estado del último comando— para la franja que carmen + /// reserva al pie. fn render_launcher(&mut self, cx: &mut Context) -> gpui::Div { let theme = Theme::global(cx).clone(); let panel = gpui::hsla(220.0 / 360.0, 0.16, 0.11, 1.0); + let node_bg = gpui::hsla(220.0 / 360.0, 0.14, 0.16, 1.0); let accent = gpui::hsla(190.0 / 360.0, 0.70, 0.62, 1.0); let dim = theme.fg_muted; let text = theme.fg_text; + // Barra de tareas: una cajita por ventana abierta, la enfocada + // resaltada. Un clic se la pide a carmen por el socket de control. + let chips: Vec<_> = self + .windows_bar + .iter() + .map(|w| { + let id = w.id; + let raw = if !w.title.is_empty() { &w.title } else { &w.app_id }; + let label = if raw.chars().count() > 18 { + format!("{}…", raw.chars().take(18).collect::()) + } else { + raw.clone() + }; + let focused = w.focused; + div() + .id(SharedString::from(format!("win-{id}"))) + .flex_none() + .px(px(8.)) + .py(px(3.)) + .rounded(px(4.)) + .text_size(px(12.)) + .cursor_pointer() + .when(focused, |d| { + d.bg(accent).text_color(gpui::hsla(0.0, 0.0, 0.12, 1.0)) + }) + .when(!focused, |d| d.bg(node_bg).text_color(dim)) + .child(SharedString::from(label)) + .on_click(cx.listener(move |shell, _, _, cx| { + focus_window(id); + // Eco inmediato — el sondeo confirma en ~1 s. + for w in &mut shell.windows_bar { + w.focused = w.id == id; + } + cx.notify(); + })) + }) + .collect(); + let taskbar = div() + .flex() + .flex_row() + .items_center() + .gap(px(4.)) + .flex_none() + .overflow_hidden() + .children(chips); + // Estado a la derecha: nº de comandos en curso, o el último. let status = if !self.active.is_empty() { div() @@ -1066,6 +1147,7 @@ impl Shell { .items_center() .gap(px(10.)) .px(px(12.)) + .overflow_hidden() .bg(panel) .text_color(text) .text_size(px(13.)) @@ -1082,6 +1164,7 @@ impl Shell { .overflow_hidden() .children(self.input_row(&theme)), ) + .child(taskbar) .child(status) } } diff --git a/crates/modules/mirada/SDD.md b/crates/modules/mirada/SDD.md index 2c88a74..abf2d76 100644 --- a/crates/modules/mirada/SDD.md +++ b/crates/modules/mirada/SDD.md @@ -241,14 +241,16 @@ Cerebro: **autónomo** (`Desktop` embebido) o **enlazado** (`MIRADA_SOCKET` | ------------------ | ---------------------------------------------------------- | | 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 | -| `shuma-shell` | modo launcher: falta la barra de ventanas y el cajón | +| `shuma-shell` | modo launcher: falta el cajón de resultados y la config | | `wlr-layer-shell` | barras externas tipo waybar, fondos, notificaciones | | `mirada-sandbox` | aislamiento de clientes sobre `arje-incarnate` | `shuma-shell --launcher` ya corre como el shell de carmen: abre una ventana sin barra de título con `app_id = "carmen.shell"` (el acople la reconoce) y dibuja una barra compacta — glifo, la línea de comandos de -`shuma-line` y el estado del último comando. Falta la barra de ventanas -abiertas (vía el socket de control) y el cajón de resultados. +`shuma-line`, la **barra de ventanas abiertas** (las consulta por el +socket de control de carmen con `ListWindows`, un clic enfoca con +`FocusWindow`) y el estado del último comando. Falta el cajón de +resultados expandible y la config para esconder/reubicar. CRIU (congelar/restaurar ventanas) queda anotado como futuro. diff --git a/vamos.txt b/vamos.txt index 6bd87bc..42aa566 100644 --- a/vamos.txt +++ b/vamos.txt @@ -1010,7 +1010,8 @@ Lanzador de apps: mirada-launcher (escanea los .desktop, lista filtrable de terminal); atado a Super+p. Conmutación de VT: Ctrl+Alt+Fn salta a otra TTY y vuelve sin romper la sesión (pausa DRM + libinput). Acople del shell: una ventana con app_id "carmen.shell" se ancla en una franja al pie; el resto tesela arriba. - shuma-shell --launcher: corre como ese shell — barra compacta GPUI con la línea de comandos de shuma-line. + shuma-shell --launcher: corre como ese shell — barra compacta GPUI con la línea de comandos + barra de ventanas. + La barra de ventanas las consulta por el socket de control de carmen (ListWindows); un clic enfoca. Sesión: ~/.config/mirada/autostart (un comando por línea) + script session/mirada-session + carmen.desktop. Ver crates/apps/mirada-compositor/README.md.