feat(mirada): reglas de ventana — escritorio y flotante por app_id
mirada-brain::rules — config declarativa que decide qué hacer con una ventana al abrirse, mismo patrón que el keymap. - Rule casa por subcadena de app_id y/o title (sin distinguir mayúsculas; vacío = cualquiera) y aplica un destino: workspace (1..9) y/o floating. Gana la primera regla que case. - Rules en RON (~/.config/mirada/rules.ron); la primera vez se escribe una plantilla con ejemplos comentados, si está corrupta se ignora. - Desktop consulta Rules::resolve en cada WindowOpened — el evento ya trae app_id/title — y abre la ventana en su escritorio, flotando si toca. set_rules en Desktop; las apps cargan rules.ron al arrancar. mirada-brain 42->51 tests. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ use mirada_protocol::{placements, BodyEvent, BrainCommand, OutputId};
|
||||
|
||||
use crate::action::{DesktopAction, WORKSPACE_COUNT};
|
||||
use crate::keymap::Keymap;
|
||||
use crate::rules::Rules;
|
||||
|
||||
/// Lo que el Cerebro sabe de una ventana: su identidad de aplicación.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
@@ -35,6 +36,8 @@ pub struct Desktop {
|
||||
windows: HashMap<WindowId, WindowInfo>,
|
||||
/// Atajos globales → acción. Configurable, recargable en caliente.
|
||||
keymap: Keymap,
|
||||
/// Reglas de ventana — escritorio/flotante por `app_id`/título.
|
||||
rules: Rules,
|
||||
}
|
||||
|
||||
impl Default for Desktop {
|
||||
@@ -62,9 +65,16 @@ impl Desktop {
|
||||
active: 0,
|
||||
windows: HashMap::new(),
|
||||
keymap,
|
||||
rules: Rules::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reemplaza las reglas de ventana. Se aplican a las ventanas que se
|
||||
/// abran a partir de ahora; las ya abiertas no se tocan.
|
||||
pub fn set_rules(&mut self, rules: Rules) {
|
||||
self.rules = rules;
|
||||
}
|
||||
|
||||
/// El comando que registra los atajos globales en el Cuerpo. La app
|
||||
/// lo envía al conectar, y de nuevo tras cada recarga del keymap.
|
||||
pub fn grab_keys(&self) -> BrainCommand {
|
||||
@@ -103,8 +113,21 @@ impl Desktop {
|
||||
self.relayout()
|
||||
}
|
||||
BodyEvent::WindowOpened { id, app_id, title } => {
|
||||
// Las reglas pueden mandarla a otro escritorio o hacerla flotar.
|
||||
let outcome = self.rules.resolve(&app_id, &title);
|
||||
self.windows.insert(id, WindowInfo { app_id, title });
|
||||
self.workspaces[self.active].add(id);
|
||||
let ws = outcome
|
||||
.workspace
|
||||
.filter(|&n| n < self.workspaces.len())
|
||||
.unwrap_or(self.active);
|
||||
self.workspaces[ws].add(id);
|
||||
if outcome.floating {
|
||||
let rect = self
|
||||
.screen()
|
||||
.map(centered_float_rect)
|
||||
.unwrap_or_else(|| Rect::new(100, 100, 800, 600));
|
||||
self.workspaces[ws].set_floating(id, Some(rect));
|
||||
}
|
||||
self.relayout()
|
||||
}
|
||||
BodyEvent::WindowClosed { id } => {
|
||||
@@ -452,6 +475,24 @@ mod tests {
|
||||
assert!(!places(&cmds).iter().find(|x| x.id == 2).unwrap().floating);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rule_sends_a_new_window_to_its_workspace() {
|
||||
let mut d = desktop_with_screen();
|
||||
d.set_rules(Rules::from_ron(r#"( rules: [ (app_id: "app2", workspace: 3) ] )"#).unwrap());
|
||||
open(&mut d, 1); // app1 → sin regla → escritorio activo (1)
|
||||
open(&mut d, 2); // app2 → regla → escritorio 3
|
||||
assert_eq!(d.workspace_loads()[0], 1);
|
||||
assert_eq!(d.workspace_loads()[2], 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rule_can_open_a_window_floating() {
|
||||
let mut d = desktop_with_screen();
|
||||
d.set_rules(Rules::from_ron(r#"( rules: [ (app_id: "app1", floating: true) ] )"#).unwrap());
|
||||
let cmds = open(&mut d, 1);
|
||||
assert!(places(&cmds).iter().find(|p| p.id == 1).unwrap().floating);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_a_screen_nothing_is_placed() {
|
||||
let mut d = Desktop::new();
|
||||
|
||||
Reference in New Issue
Block a user